From d67ce9b1ffcec0693effb346a01d28840924ec61 Mon Sep 17 00:00:00 2001 From: Killers0992 <38152961+Killers0992@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:28:05 +0200 Subject: [PATCH] Add new return type for PreAuth and fix analyzers (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add analyzers * Add analyzers * Added more advanced event cancellation. * Fix * Added manual handling of preauth event * Update API Co-authored-by: Łukasz Jurczyk --- .../CodeFixResources.Designer.cs | 72 --- .../CodeFixResources.resx | 124 ------ .../NWPluginAPI.Analyzers.CodeFixes.csproj | 22 - .../NWPluginAPIAnalyzersCodeFixProvider.cs | 63 --- .../NWPluginAPI.Analyzers/Enums/ActionType.cs | 10 + .../Generated/GeneratedEventManager.cs | 214 +++++++++ .../NWPluginAPI.Analyzers.csproj | 14 +- .../NWPluginAPIAnalyzersAnalyzer.cs | 174 +++++++- .../NWPluginAPIAnalyzersCodeFixProvider.cs | 144 ++++++ .../Resources.Designer.cs | 105 ----- .../NWPluginAPI.Analyzers/Resources.resx | 132 ------ .../NwPluginAPI.Analyzers.Generator.csproj | 13 + .../Program.cs | 58 +++ NwPluginAPI.sln | 14 +- NwPluginAPI.sln.DotSettings | 2 + NwPluginAPI/Core/FactoryManager.cs | 24 +- NwPluginAPI/Core/Player.cs | 15 +- NwPluginAPI/Events/Event.cs | 31 ++ NwPluginAPI/Events/EventInvokeLocation.cs | 15 + NwPluginAPI/Events/EventManager.cs | 413 ++++++++++++------ NwPluginAPI/Events/EventParameter.cs | 16 + NwPluginAPI/Events/IEventCancellation.cs | 180 ++++++++ NwPluginAPI/Loader/AssemblyLoader.cs | 4 + TemplatePlugin/MainClass.cs | 5 +- TemplatePlugin/TemplatePlugin.csproj | 4 +- 25 files changed, 1174 insertions(+), 694 deletions(-) delete mode 100644 Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.Designer.cs delete mode 100644 Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.resx delete mode 100644 Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPI.Analyzers.CodeFixes.csproj delete mode 100644 Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPIAnalyzersCodeFixProvider.cs create mode 100644 Analyzers/NWPluginAPI.Analyzers/Enums/ActionType.cs create mode 100644 Analyzers/NWPluginAPI.Analyzers/Generated/GeneratedEventManager.cs create mode 100644 Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersCodeFixProvider.cs delete mode 100644 Analyzers/NWPluginAPI.Analyzers/Resources.Designer.cs delete mode 100644 Analyzers/NWPluginAPI.Analyzers/Resources.resx create mode 100644 Analyzers/NwPluginAPI.Analyzers.Generator/NwPluginAPI.Analyzers.Generator.csproj create mode 100644 Analyzers/NwPluginAPI.Analyzers.Generator/Program.cs create mode 100644 NwPluginAPI.sln.DotSettings create mode 100644 NwPluginAPI/Events/Event.cs create mode 100644 NwPluginAPI/Events/EventInvokeLocation.cs create mode 100644 NwPluginAPI/Events/EventParameter.cs create mode 100644 NwPluginAPI/Events/IEventCancellation.cs diff --git a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.Designer.cs b/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.Designer.cs deleted file mode 100644 index 06cf466..0000000 --- a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NWPluginAPI.Analyzers { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class CodeFixResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal CodeFixResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NWPluginAPI.Analyzers.CodeFixResources", typeof(CodeFixResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Make uppercase. - /// - internal static string CodeFixTitle { - get { - return ResourceManager.GetString("CodeFixTitle", resourceCulture); - } - } - } -} diff --git a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.resx b/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.resx deleted file mode 100644 index 97abe68..0000000 --- a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/CodeFixResources.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Make uppercase - The title of the code fix. - - \ No newline at end of file diff --git a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPI.Analyzers.CodeFixes.csproj b/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPI.Analyzers.CodeFixes.csproj deleted file mode 100644 index ae3a5f5..0000000 --- a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPI.Analyzers.CodeFixes.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - netstandard2.0 - false - NWPluginAPI.Analyzers - - - - - - - - - - - - - - - - diff --git a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPIAnalyzersCodeFixProvider.cs b/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPIAnalyzersCodeFixProvider.cs deleted file mode 100644 index 5a47404..0000000 --- a/Analyzers/NWPluginAPI.Analyzers.CodeFixes/NWPluginAPIAnalyzersCodeFixProvider.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NWPluginAPI.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NWPluginAPIAnalyzersCodeFixProvider)), Shared] - public class NWPluginAPIAnalyzersCodeFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(NWPluginAPIAnalyzersAnalyzer.DiagnosticId); } - } - - public sealed override FixAllProvider GetFixAllProvider() - { - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - // Find the type declaration identified by the diagnostic. - var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - - // Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title: "Add missing parameters", - createChangedDocument: c => MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey: nameof(CodeFixResources.CodeFixTitle)), - diagnostic); - } - - private async Task MakeUppercaseAsync(Document document, MethodDeclarationSyntax param, CancellationToken cancellationToken) - { - var attribute = SyntaxFactory.AttributeList( - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("PluginEvent"), null))); - - var newParam = param.WithAttributeLists(param.AttributeLists.Add(attribute)); - - var root = await document.GetSyntaxRootAsync(); - var newRoot = root.ReplaceNode(param, newParam); - var newDocument = document.WithSyntaxRoot(newRoot); - - return newDocument; - } - } -} diff --git a/Analyzers/NWPluginAPI.Analyzers/Enums/ActionType.cs b/Analyzers/NWPluginAPI.Analyzers/Enums/ActionType.cs new file mode 100644 index 0000000..0546c2b --- /dev/null +++ b/Analyzers/NWPluginAPI.Analyzers/Enums/ActionType.cs @@ -0,0 +1,10 @@ +namespace NWPluginAPI.Analyzers.Enums +{ + public enum ActionType + { + Add, + Replace, + Remove, + None + } +} diff --git a/Analyzers/NWPluginAPI.Analyzers/Generated/GeneratedEventManager.cs b/Analyzers/NWPluginAPI.Analyzers/Generated/GeneratedEventManager.cs new file mode 100644 index 0000000..64cd7c2 --- /dev/null +++ b/Analyzers/NWPluginAPI.Analyzers/Generated/GeneratedEventManager.cs @@ -0,0 +1,214 @@ +using System.Collections.Generic; + +public class Event +{ + public readonly EventParameter[] Parameters; + + public Event(params EventParameter[] parameters) + { + Parameters = parameters; + } +} + +public class EventParameter +{ + public string BaseType { get; set; } + public bool IsArray { get; set; } + public string DefaultIdentifierName { get; set; } + + public EventParameter(string baseType, bool isArray, string defaultIdentifierName) + { + BaseType = baseType; + IsArray = isArray; + DefaultIdentifierName = defaultIdentifierName; + } +} + +public static class EventManager +{ + public static Dictionary Events = new Dictionary() + { + { 0, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 1, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 2, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "attacker"), + new EventParameter("PlayerStatsSystem.DamageHandlerBase", false, "damageHandler")) }, + { 3, new Event() }, + { 4, new Event( + new EventParameter("System.Int32", false, "id")) }, + { 5, new Event() }, + { 6, new Event( + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "grenade")) }, + { 7, new Event( + new EventParameter("ItemType", false, "item")) }, + { 8, new Event( + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 9, new Event() }, + { 10, new Event() }, + { 11, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 12, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Firearms.Firearm", false, "firearm"), + new EventParameter("System.Boolean", false, "isAiming")) }, + { 13, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "issuer"), + new EventParameter("System.String", false, "reason"), + new EventParameter("System.Int64", false, "duration")) }, + { 14, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Usables.UsableItem", false, "item")) }, + { 15, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("System.UInt16", false, "oldItem"), + new EventParameter("System.UInt16", false, "newItem")) }, + { 16, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Radio.RadioItem", false, "radio"), + new EventParameter("System.Byte", false, "range")) }, + { 17, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "oldTarget"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "newTarget")) }, + { 18, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 19, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("AdminToys.ShootingTarget", false, "shootingTarget"), + new EventParameter("PlayerStatsSystem.DamageHandlerBase", false, "damageHandler"), + new EventParameter("System.Single", false, "damageAmount")) }, + { 20, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("BreakableWindow", false, "window"), + new EventParameter("PlayerStatsSystem.DamageHandlerBase", false, "damageHandler"), + new EventParameter("System.Single", false, "damageAmount")) }, + { 21, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 22, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("ItemType", false, "item"), + new EventParameter("System.Int32", false, "amount")) }, + { 23, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.ItemBase", false, "item")) }, + { 24, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Firearms.Firearm", false, "firearm")) }, + { 25, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PlayerRoles.RoleTypeId", false, "newRole")) }, + { 26, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "target")) }, + { 27, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "target")) }, + { 28, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "target"), + new EventParameter("PlayerStatsSystem.DamageHandlerBase", false, "damageHandler")) }, + { 29, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 30, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 31, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 32, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 33, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "issuer"), + new EventParameter("System.String", false, "reason")) }, + { 34, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player")) }, + { 35, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 36, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "item")) }, + { 37, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "item")) }, + { 38, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "item")) }, + { 39, new Event( + new EventParameter("System.String", false, "userId"), + new EventParameter("System.String", false, "ipAddress"), + new EventParameter("System.Int64", false, "expiration"), + new EventParameter("CentralAuthPreauthFlags", false, "centralFlags"), + new EventParameter("System.String", false, "region"), + new EventParameter("System.Byte", true, "signature"), + new EventParameter("LiteNetLib.ConnectionRequest", false, "connectionRequest"), + new EventParameter("System.Int32", false, "readerStartPosition")) }, + { 40, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("CustomPlayerEffects.PlayerEffect", false, "effect")) }, + { 41, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Firearms.Firearm", false, "firearm")) }, + { 42, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PlayerRoles.PlayerRoleBase", false, "oldRole"), + new EventParameter("PlayerRoles.PlayerRoleBase", false, "newRole"), + new EventParameter("PlayerRoles.RoleChangeReason", false, "changeReason")) }, + { 43, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "item")) }, + { 44, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Pickups.ItemPickupBase", false, "item")) }, + { 45, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Firearms.Firearm", false, "firearm")) }, + { 46, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PlayerRoles.RoleTypeId", false, "role")) }, + { 47, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PlayerRoles.IRagdollRole", false, "ragdoll"), + new EventParameter("PlayerStatsSystem.DamageHandlerBase", false, "damageHandler")) }, + { 48, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.ItemBase", false, "item")) }, + { 49, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.ItemBase", false, "item"), + new EventParameter("System.Boolean", false, "isToggled")) }, + { 50, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Firearms.Firearm", false, "firearm")) }, + { 51, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("MapGeneration.Distributors.Scp079Generator", false, "generator")) }, + { 52, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.ItemBase", false, "item")) }, + { 53, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("ActionName", false, "action")) }, + { 54, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("InventorySystem.Items.Usables.UsableItem", false, "item")) }, + { 55, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "target"), + new EventParameter("System.String", false, "reason")) }, + { 56, new Event( + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "player"), + new EventParameter("PluginAPI.Core.Interfaces.IPlayer", false, "target"), + new EventParameter("System.String", false, "reason")) }, + { 57, new Event() }, + { 58, new Event() }, + { 59, new Event() }, + { 60, new Event() }, + }; +} diff --git a/Analyzers/NWPluginAPI.Analyzers/NWPluginAPI.Analyzers.csproj b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPI.Analyzers.csproj index 7176c81..fbc4881 100644 --- a/Analyzers/NWPluginAPI.Analyzers/NWPluginAPI.Analyzers.csproj +++ b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPI.Analyzers.csproj @@ -1,21 +1,13 @@ - + netstandard2.0 false - - - *$(MSBuildProjectFile)* - + + - - - - - - diff --git a/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersAnalyzer.cs b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersAnalyzer.cs index e626438..17dc98d 100644 --- a/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersAnalyzer.cs +++ b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersAnalyzer.cs @@ -1,41 +1,187 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using NWPluginAPI.Analyzers.Enums; +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; namespace NWPluginAPI.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class NWPluginAPIAnalyzersAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "NWPluginAPIAnalyzers"; + public const string ParameterNotRequiredDiagnosticId = "NWAPIRP"; + public const string WrongParameterDiagnosticId = "NWAPIWP"; + public const string MissingParameterDiagnosticId = "NWAPIMP"; + public const string MissingParametersDiagnosticId = "NWAPIMPS"; - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Naming"; - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + private static readonly DiagnosticDescriptor ParameterNotRequiredRule = new DiagnosticDescriptor(ParameterNotRequiredDiagnosticId, + "Parameter not required", + "Parameter '{0}' is not used in this event!", + "Naming", + DiagnosticSeverity.Error, + isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + private static readonly DiagnosticDescriptor WrongParameterRule = new DiagnosticDescriptor(WrongParameterDiagnosticId, + "Wrong type used", + "Type '{0}' is not used in this event, use type '{1}'!", + "Naming", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor MissingParameterRule = new DiagnosticDescriptor(MissingParameterDiagnosticId, + "Missing parameter", + "Parameter of type '{0}' is missing in this event!", + "Naming", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor MissingParametersRule = new DiagnosticDescriptor(MissingParametersDiagnosticId, + "Missing parameters", + "Parameters of type '{0}' are missing in this event!", + "Naming", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + + public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(ParameterNotRequiredRule, WrongParameterRule, MissingParameterRule, MissingParametersRule); } } public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); - context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method); } private static void AnalyzeSymbol(SymbolAnalysisContext context) { - // TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find - var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; + var methodSymbol = (IMethodSymbol)context.Symbol; + + INamedTypeSymbol attribute = context.Compilation.GetTypeByMetadataName("PluginAPI.Core.Attributes.PluginEvent"); + + INamedTypeSymbol eventTypeEnum = context.Compilation.GetTypeByMetadataName("PluginAPI.Enums.ServerEventType"); + + var eventAttribute = methodSymbol.GetAttributes().FirstOrDefault(x => SymbolEqualityComparer.Default.Equals(x.AttributeClass, attribute)); + + if (eventAttribute == null) return; + + var eventType = eventAttribute.ConstructorArguments.FirstOrDefault(x => SymbolEqualityComparer.Default.Equals(x.Type, eventTypeEnum)); + + if (eventType.IsNull) return; + + if (!(eventType.Value is int eventNum)) return; + + if (!EventManager.Events.TryGetValue(eventNum, out Event ev)) return; + + List requiredSymbols = new List(); + + for (int x = 0; x < ev.Parameters.Length; x++) + { + requiredSymbols.Add(context.Compilation.GetTypeByMetadataName(ev.Parameters[x].BaseType)); + } + + List result = new List(); + + for (int x = 0; x < ev.Parameters.Length; x++) + { + result.Add(ActionType.Add); + } + + for (int x = 0; x < methodSymbol.Parameters.Length; x++) + { + var parameter = methodSymbol.Parameters[x]; + + if (requiredSymbols.Count < x+1) + { + var diagTooMuchParams = Diagnostic.Create(ParameterNotRequiredRule, parameter.Locations[0], parameter.Name); + + context.ReportDiagnostic(diagTooMuchParams); + result.Add(ActionType.Remove); + continue; + } + + if (parameter.Type.TypeKind == requiredSymbols[x].TypeKind) + { + if (parameter.Type.SpecialType == SpecialType.System_Array && ev.Parameters[x].IsArray) + { + result[x] = ActionType.None; + continue; + } + + if (ev.Parameters[x].IsArray) + { + var diagReplace = Diagnostic.Create(WrongParameterRule, parameter.Locations[0], parameter.Type.Name, requiredSymbols[x].Name); + + context.ReportDiagnostic(diagReplace); + result[x] = ActionType.Replace; + continue; + } + } + else + { + if (requiredSymbols[x].AllInterfaces.Length != 0) + { + if (context.Compilation.IsSymbolAccessibleWithin(requiredSymbols[x], parameter.Type)) + { + result[x] = ActionType.None; + continue; + } + } + + var diagWrongParam = Diagnostic.Create(WrongParameterRule, parameter.Locations[0], parameter.Type.Name, requiredSymbols[x].Name); + + context.ReportDiagnostic(diagWrongParam); + result[x] = ActionType.Replace; + continue; + } + + result[x] = ActionType.None; + } + + Dictionary missingParameters = new Dictionary(); + + for (int x = 0; x < result.Count; x++) + { + if (result[x] != ActionType.Add) continue; + + missingParameters.Add(x, ev.Parameters[x]); + } + + if (missingParameters.Count != 0) + { + Dictionary paramsToAdd = new Dictionary() + { + { "eventId", eventNum.ToString() } + }; + + string missingParams = string.Empty; + string missingParamsStr = string.Empty; + + foreach (var missingParam in missingParameters) + { + missingParams += $"{missingParam.Key},"; + missingParamsStr += $"{missingParam.Value.BaseType}, "; + } + + missingParams = missingParams.Substring(0, missingParams.Length - 1); + + paramsToAdd.Add("parameters", missingParams); - var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); + missingParamsStr = missingParamsStr.Substring(0, missingParamsStr.Length - 2); - context.ReportDiagnostic(diagnostic); + if (missingParams.Length == 1) + { + var diagMissingParam = Diagnostic.Create(MissingParameterRule, methodSymbol.Locations[0], ImmutableDictionary.CreateRange(paramsToAdd), missingParamsStr); + context.ReportDiagnostic(diagMissingParam); + } + else + { + var diagMissingParams = Diagnostic.Create(MissingParametersRule, methodSymbol.Locations[0], ImmutableDictionary.CreateRange(paramsToAdd), missingParamsStr); + context.ReportDiagnostic(diagMissingParams); + } + } } } } diff --git a/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersCodeFixProvider.cs b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersCodeFixProvider.cs new file mode 100644 index 0000000..578f8e7 --- /dev/null +++ b/Analyzers/NWPluginAPI.Analyzers/NWPluginAPIAnalyzersCodeFixProvider.cs @@ -0,0 +1,144 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace NWPluginAPI.Analyzers +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NWPluginAPIAnalyzersCodeFixProvider)), Shared] + public class NWPluginAPIAnalyzersCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(NWPluginAPIAnalyzersAnalyzer.WrongParameterDiagnosticId, NWPluginAPIAnalyzersAnalyzer.MissingParameterDiagnosticId, NWPluginAPIAnalyzersAnalyzer.MissingParametersDiagnosticId, NWPluginAPIAnalyzersAnalyzer.ParameterNotRequiredDiagnosticId); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + foreach(var diagnostic in context.Diagnostics) + { + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + switch (diagnostic.Id) + { + case NWPluginAPIAnalyzersAnalyzer.WrongParameterDiagnosticId: + context.RegisterCodeFix( + CodeAction.Create( + title: "Replace parameter", + createChangedDocument: c => ReplaceParameterAsync(context.Document, diagnostic.Properties, declaration, c), + equivalenceKey: "Replace parameter"), + diagnostic); + break; + case NWPluginAPIAnalyzersAnalyzer.MissingParameterDiagnosticId: + context.RegisterCodeFix( + CodeAction.Create( + title: "Add parameter", + createChangedDocument: c => AddParameterAsync(context.Document, diagnostic.Properties, declaration, c), + equivalenceKey: "Add parameter"), + diagnostic); + break; + case NWPluginAPIAnalyzersAnalyzer.MissingParametersDiagnosticId: + context.RegisterCodeFix( + CodeAction.Create( + title: "Add parameters", + createChangedDocument: c => AddParametersAsync(context.Document, diagnostic.Properties, declaration, c), + equivalenceKey: "Add parameters"), + diagnostic); + break; + } + } + } + + private async Task ReplaceParameterAsync(Document document, ImmutableDictionary properties, MethodDeclarationSyntax method, CancellationToken token) + { + var root = await document.GetSyntaxRootAsync(); + + var updatedMethod = method.AddParameterListParameters( + SyntaxFactory.Parameter( + SyntaxFactory.Identifier("amogus")) + .WithType(SyntaxFactory.ParseTypeName("PluginAPI.Core.Player"))); + + var updatedSyntaxTree = root.ReplaceNode(method, updatedMethod); + + return document.WithSyntaxRoot(updatedSyntaxTree); + } + + private async Task AddParameterAsync(Document document, ImmutableDictionary properties, MethodDeclarationSyntax method, CancellationToken token) + { + var root = await document.GetSyntaxRootAsync(); + + if (!properties.TryGetValue("eventId", out string rawEventId)) return document; + + if (!int.TryParse(rawEventId, out int eventId)) return document; + + if (!EventManager.Events.TryGetValue(eventId, out Event ev)) return document; + + if (!properties.TryGetValue("parameters", out string rawParam)) return document; + + if (!int.TryParse(rawParam, out int parameterIndex)) return document; + + var updatedMethod = method.AddParameterListParameters( + SyntaxFactory.Parameter( + SyntaxFactory.Identifier( + ev.Parameters[parameterIndex].DefaultIdentifierName)).WithType( + SyntaxFactory.ParseTypeName(ev.Parameters[parameterIndex].BaseType + (ev.Parameters[parameterIndex].IsArray ? "[]" : string.Empty)) + .WithAdditionalAnnotations(Simplifier.Annotation))); + + var updatedSyntaxTree = root.ReplaceNode(method, updatedMethod); + + return document.WithSyntaxRoot(updatedSyntaxTree); + } + + private async Task AddParametersAsync(Document document, ImmutableDictionary properties, MethodDeclarationSyntax method, CancellationToken token) + { + var root = await document.GetSyntaxRootAsync(); + + if (!properties.TryGetValue("eventId", out string rawEventId)) return document; + + if (!int.TryParse(rawEventId, out int eventId)) return document; + + if (!EventManager.Events.TryGetValue(eventId, out Event ev)) return document; + + if (!properties.TryGetValue("parameters", out string rawParam)) return document; + + var indexes = rawParam.Split(',').Select(x => int.Parse(x)); + + List parameters = new List(); + + foreach (var index in indexes) + { + parameters.Add( + SyntaxFactory.Parameter( + SyntaxFactory.Identifier( + ev.Parameters[index].DefaultIdentifierName)).WithType( + SyntaxFactory.ParseTypeName(ev.Parameters[index].BaseType + (ev.Parameters[index].IsArray ? "[]" : string.Empty)) + .WithAdditionalAnnotations(Simplifier.Annotation))); + } + + MethodDeclarationSyntax updatedMethod = method.AddParameterListParameters(parameters.ToArray()); + + var updatedSyntaxTree = root.ReplaceNode(method, updatedMethod); + + return document.WithSyntaxRoot(updatedSyntaxTree); + } + } +} diff --git a/Analyzers/NWPluginAPI.Analyzers/Resources.Designer.cs b/Analyzers/NWPluginAPI.Analyzers/Resources.Designer.cs deleted file mode 100644 index 7e3a20d..0000000 --- a/Analyzers/NWPluginAPI.Analyzers/Resources.Designer.cs +++ /dev/null @@ -1,105 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -namespace NWPluginAPI.Analyzers -{ - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NWPluginAPI.Analyzers.Resources", typeof(Resources).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Type names should be all uppercase.. - /// - internal static string AnalyzerDescription - { - get - { - return ResourceManager.GetString("AnalyzerDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. - /// - internal static string AnalyzerMessageFormat - { - get - { - return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name contains lowercase letters. - /// - internal static string AnalyzerTitle - { - get - { - return ResourceManager.GetString("AnalyzerTitle", resourceCulture); - } - } - } -} diff --git a/Analyzers/NWPluginAPI.Analyzers/Resources.resx b/Analyzers/NWPluginAPI.Analyzers/Resources.resx deleted file mode 100644 index 410edcc..0000000 --- a/Analyzers/NWPluginAPI.Analyzers/Resources.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Type names should be all uppercase. - An optional longer localizable description of the diagnostic. - - - Type name '{0}' contains lowercase letters - The format-able message the diagnostic displays. - - - Type name contains lowercase letters - The title of the diagnostic. - - \ No newline at end of file diff --git a/Analyzers/NwPluginAPI.Analyzers.Generator/NwPluginAPI.Analyzers.Generator.csproj b/Analyzers/NwPluginAPI.Analyzers.Generator/NwPluginAPI.Analyzers.Generator.csproj new file mode 100644 index 0000000..1670ef6 --- /dev/null +++ b/Analyzers/NwPluginAPI.Analyzers.Generator/NwPluginAPI.Analyzers.Generator.csproj @@ -0,0 +1,13 @@ + + + + Exe + net6.0 + enable + enable + + + + + + diff --git a/Analyzers/NwPluginAPI.Analyzers.Generator/Program.cs b/Analyzers/NwPluginAPI.Analyzers.Generator/Program.cs new file mode 100644 index 0000000..d9613ff --- /dev/null +++ b/Analyzers/NwPluginAPI.Analyzers.Generator/Program.cs @@ -0,0 +1,58 @@ +using PluginAPI.Events; +using System.Text; + +StringBuilder builder = new StringBuilder(); +builder.AppendLine("using System.Collections.Generic;"); +builder.AppendLine(string.Empty); +builder.AppendLine("public class Event"); +builder.AppendLine("{"); +builder.AppendLine(" public readonly EventParameter[] Parameters;"); +builder.AppendLine(string.Empty); +builder.AppendLine(" public Event(params EventParameter[] parameters)"); +builder.AppendLine(" {"); +builder.AppendLine(" Parameters = parameters;"); +builder.AppendLine(" }"); +builder.AppendLine("}"); +builder.AppendLine(string.Empty); +builder.AppendLine("public class EventParameter"); +builder.AppendLine("{"); +builder.AppendLine(" public string BaseType { get; set; }"); +builder.AppendLine(" public bool IsArray { get; set; }"); +builder.AppendLine(" public string DefaultIdentifierName { get; set; }"); +builder.AppendLine(string.Empty); +builder.AppendLine(" public EventParameter(string baseType, bool isArray, string defaultIdentifierName)"); +builder.AppendLine(" {"); +builder.AppendLine(" BaseType = baseType;"); +builder.AppendLine(" IsArray = isArray;"); +builder.AppendLine(" DefaultIdentifierName = defaultIdentifierName;"); +builder.AppendLine(" }"); +builder.AppendLine("}"); +builder.AppendLine(string.Empty); +builder.AppendLine("public static class EventManager"); +builder.AppendLine("{"); +builder.AppendLine(" public static Dictionary Events = new Dictionary()"); +builder.AppendLine(" {"); + +foreach (var ev in EventManager.Events) +{ + if (ev.Value.Parameters.Length == 0) + builder.AppendLine(" { " + (int)ev.Key + ", new Event() },"); + else + { + builder.AppendLine(" { " + (int)ev.Key + ", new Event("); + for(int x = 0; x < ev.Value.Parameters.Length; x++) + { + var param = ev.Value.Parameters[x]; + + if (param.BaseType.IsArray) + builder.AppendLine(" new EventParameter(\"" + param.BaseType.GetElementType()?.FullName + "\", true, \"" + param.DefaultIdentifierName+ "\")" + (x == (ev.Value.Parameters.Length - 1) ? ") }," : ",")); + else + builder.AppendLine(" new EventParameter(\"" + param.BaseType.FullName + "\", false, \"" + param.DefaultIdentifierName+ "\")" + (x == (ev.Value.Parameters.Length - 1) ? ") }," : ",")); + } + } +} + +builder.AppendLine(" };"); +builder.AppendLine("}"); + +File.WriteAllText($"..\\..\\..\\..\\NWPluginAPI.Analyzers\\Generated\\GeneratedEventManager.cs", builder.ToString()); diff --git a/NwPluginAPI.sln b/NwPluginAPI.sln index 4338137..82e60a4 100644 --- a/NwPluginAPI.sln +++ b/NwPluginAPI.sln @@ -14,9 +14,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemplatePlugin", "TemplateP EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{3CA0C582-BA36-455A-A480-8DD78BDD24D0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NWPluginAPI.Analyzers", "Analyzers\NWPluginAPI.Analyzers\NWPluginAPI.Analyzers.csproj", "{4BE5D776-39A9-4532-9390-C59FB45E9571}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NwPluginAPI.Analyzers", "Analyzers\NWPluginAPI.Analyzers\NwPluginAPI.Analyzers.csproj", "{4BE5D776-39A9-4532-9390-C59FB45E9571}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NWPluginAPI.Analyzers.CodeFixes", "Analyzers\NWPluginAPI.Analyzers.CodeFixes\NWPluginAPI.Analyzers.CodeFixes.csproj", "{2044B87B-5C3C-478A-8048-452367690DF8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NwPluginAPI.Analyzers.Generator", "Analyzers\NwPluginAPI.Analyzers.Generator\NwPluginAPI.Analyzers.Generator.csproj", "{9638A1ED-142B-4487-8DAA-A24E1F42E04C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -36,17 +36,17 @@ Global {4BE5D776-39A9-4532-9390-C59FB45E9571}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BE5D776-39A9-4532-9390-C59FB45E9571}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BE5D776-39A9-4532-9390-C59FB45E9571}.Release|Any CPU.Build.0 = Release|Any CPU - {2044B87B-5C3C-478A-8048-452367690DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2044B87B-5C3C-478A-8048-452367690DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2044B87B-5C3C-478A-8048-452367690DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2044B87B-5C3C-478A-8048-452367690DF8}.Release|Any CPU.Build.0 = Release|Any CPU + {9638A1ED-142B-4487-8DAA-A24E1F42E04C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9638A1ED-142B-4487-8DAA-A24E1F42E04C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9638A1ED-142B-4487-8DAA-A24E1F42E04C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9638A1ED-142B-4487-8DAA-A24E1F42E04C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {4BE5D776-39A9-4532-9390-C59FB45E9571} = {3CA0C582-BA36-455A-A480-8DD78BDD24D0} - {2044B87B-5C3C-478A-8048-452367690DF8} = {3CA0C582-BA36-455A-A480-8DD78BDD24D0} + {9638A1ED-142B-4487-8DAA-A24E1F42E04C} = {3CA0C582-BA36-455A-A480-8DD78BDD24D0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1737D319-028C-47BB-8B5F-7E1F5DEB9264} diff --git a/NwPluginAPI.sln.DotSettings b/NwPluginAPI.sln.DotSettings new file mode 100644 index 0000000..643e12a --- /dev/null +++ b/NwPluginAPI.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/NwPluginAPI/Core/FactoryManager.cs b/NwPluginAPI/Core/FactoryManager.cs index ef44f82..24d7ff4 100644 --- a/NwPluginAPI/Core/FactoryManager.cs +++ b/NwPluginAPI/Core/FactoryManager.cs @@ -36,10 +36,16 @@ public static void RegisterPlayerFactory(object plugin, PlayerFactory factory) PlayerFactories.Add(type, factory); } + /// + /// Registers new player factory. + /// + /// The plugin object. + public static void RegisterPlayerFactory(object plugin) where T : PlayerFactory => RegisterPlayerFactory(plugin, Activator.CreateInstance()); + /// /// Initializes factory manager. /// - public static void Init() + public static void Init() { StaticUnityMethods.OnUpdate += OnUpdate; StaticUnityMethods.OnLateUpdate += OnLateUpdate; @@ -53,7 +59,14 @@ static void OnUpdate() { foreach(var entity in factory.Entities) { - entity.Value.OnUpdate(); + try + { + entity.Value.OnUpdate(); + } + catch (Exception ex) + { + Log.Error($"Failed executing OnUpdate in {entity.Value.GetType().Name}, error\n {ex}"); + } } } } @@ -103,14 +116,17 @@ static void RemovePlayer(ReferenceHub obj) try { - PlayerSharedStorage.DestroyStorage(plr); plr.OnDestroy(); } catch (Exception ex) { Log.Error($"Failed executing OnDestroy in {plr.GetType().Name}, error\n {ex}"); } - factory.Entities.Remove(obj); + + if (plr is Player p) + p.OnInternalDestroy(); + + factory.Entities.Remove(obj); } } } diff --git a/NwPluginAPI/Core/Player.cs b/NwPluginAPI/Core/Player.cs index 46c453b..81fb5bb 100644 --- a/NwPluginAPI/Core/Player.cs +++ b/NwPluginAPI/Core/Player.cs @@ -493,7 +493,7 @@ public ItemBase CurrentItem public bool IsIntercomMuted => VoiceChatMutes.QueryLocalMute(UserId, true); /// - /// Gets if player is using voiec chat. + /// Gets if player is using voicechat. /// public bool IsUsingVoiceChat => PersonalRadioPlayback.IsTransmitting(ReferenceHub); @@ -617,6 +617,9 @@ public Player(IGameComponent component) EffectsManager = new EffectsManager(this); DamageManager = new DamageManager(this); + if (!PlayersIds.ContainsKey(PlayerId)) + PlayersIds.Add(PlayerId, ReferenceHub); + try { OnStart(); @@ -628,6 +631,16 @@ public Player(IGameComponent component) } #endregion + #region Internal Methods + internal void OnInternalDestroy() + { + PlayerSharedStorage.DestroyStorage(this); + PlayersIds.Remove(PlayerId); + if (UserId != null) + PlayersUserIds.Remove(UserId); + } + #endregion + #region Public Methods /// /// Shows a broadcast to the player. diff --git a/NwPluginAPI/Events/Event.cs b/NwPluginAPI/Events/Event.cs new file mode 100644 index 0000000..7693e94 --- /dev/null +++ b/NwPluginAPI/Events/Event.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace PluginAPI.Events +{ + public class Event + { + public readonly Dictionary> Invokers = new Dictionary>(); + + public readonly EventParameter[] Parameters; + + public Event(params EventParameter[] parameters) + { + Parameters = parameters; + } + + public void RegisterInvoker(Type plugin, object handle, MethodInfo method) + { + if (!Invokers.ContainsKey(plugin)) + Invokers.Add(plugin, new List()); + + Invokers[plugin].Add(new EventInvokeLocation() + { + Plugin = plugin, + Target = handle, + Method = method + }); + } + } +} diff --git a/NwPluginAPI/Events/EventInvokeLocation.cs b/NwPluginAPI/Events/EventInvokeLocation.cs new file mode 100644 index 0000000..3d41413 --- /dev/null +++ b/NwPluginAPI/Events/EventInvokeLocation.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace PluginAPI.Events +{ + public class EventInvokeLocation + { + public Type Plugin; + public object Target; + public MethodInfo Method; + + public object Invoke(List parameters) => Method.Invoke(Target, parameters.ToArray()); + } +} diff --git a/NwPluginAPI/Events/EventManager.cs b/NwPluginAPI/Events/EventManager.cs index afa7f39..e7ad6d4 100644 --- a/NwPluginAPI/Events/EventManager.cs +++ b/NwPluginAPI/Events/EventManager.cs @@ -1,3 +1,5 @@ +using LiteNetLib; + namespace PluginAPI.Events { using System; @@ -22,89 +24,199 @@ namespace PluginAPI.Events public static class EventManager { - private class EventInfo - { - public Type Plugin; - public object Target; - public MethodInfo Method; - } - private class IndexInfo { public int Index; public Type Type; } - private static readonly Dictionary> EventHandlers = new Dictionary>(); + private static readonly Dictionary EventHandlers = new Dictionary(); - private static readonly Dictionary HandlerInstances = new Dictionary(); - - private static readonly Dictionary> RegisteredEvents = new Dictionary>(); - - private static readonly Dictionary RequiredParameters = new Dictionary() + public static readonly Dictionary Events = new Dictionary() { - { ServerEventType.PlayerJoined, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerLeft, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerDeath, new[] { typeof(IPlayer), typeof(IPlayer), typeof(DamageHandlerBase) } }, - { ServerEventType.LczDecontaminationStart, Type.EmptyTypes }, - { ServerEventType.LczDecontaminationAnnouncement, new[] { typeof(int) } }, - { ServerEventType.MapGenerated, Type.EmptyTypes }, - { ServerEventType.GrenadeExploded, new[] { typeof(ItemPickupBase) } }, - { ServerEventType.ItemSpawned, new[] { typeof(ItemType) } }, - { ServerEventType.GeneratorActivated, new[] { typeof(Scp079Generator) } }, - { ServerEventType.PlaceBlood, Type.EmptyTypes }, - { ServerEventType.PlaceBulletHole, Type.EmptyTypes }, - { ServerEventType.PlayerActivateGenerator, new[] { typeof(IPlayer), typeof(Scp079Generator) } }, - { ServerEventType.PlayerAimWeapon, new[] { typeof(IPlayer), typeof(Firearm), typeof(bool) } }, - { ServerEventType.PlayerBanned, new[] { typeof(IPlayer), typeof(IPlayer), typeof(string), typeof(long) } }, - { ServerEventType.PlayerCancelUsingItem, new[] { typeof(IPlayer), typeof(UsableItem) } }, - { ServerEventType.PlayerChangeItem, new[] { typeof(IPlayer), typeof(ushort), typeof(ushort) } }, - { ServerEventType.PlayerChangeRadioRange, new[] { typeof(IPlayer), typeof(RadioItem), typeof(byte) } }, - { ServerEventType.PlayerChangeSpectator, new[] { typeof(IPlayer), typeof(IPlayer), typeof(IPlayer) } }, - { ServerEventType.PlayerCloseGenerator, new[] { typeof(IPlayer), typeof(Scp079Generator) } }, - { ServerEventType.PlayerDamagedShootingTarget, new[] { typeof(IPlayer), typeof(ShootingTarget), typeof(DamageHandlerBase), typeof(float) } }, - { ServerEventType.PlayerDamagedWindow, new[] { typeof(IPlayer), typeof(BreakableWindow), typeof(DamageHandlerBase), typeof(float) } }, - { ServerEventType.PlayerDeactivatedGenerator, new[] { typeof(IPlayer), typeof(Scp079Generator) } }, - { ServerEventType.PlayerDropAmmo, new[] { typeof(IPlayer), typeof(ItemType), typeof(int) } }, - { ServerEventType.PlayerDropItem, new[] { typeof(IPlayer), typeof(ItemBase) } }, - { ServerEventType.PlayerDryfireWeapon, new[] { typeof(IPlayer), typeof(Firearm) } }, - { ServerEventType.PlayerEscape, new[] { typeof(IPlayer), typeof(RoleTypeId) } }, - { ServerEventType.PlayerHandcuff, new[] { typeof(IPlayer), typeof(IPlayer) } }, - { ServerEventType.PlayerRemoveHandcuffs, new[] { typeof(IPlayer), typeof(IPlayer) } }, - { ServerEventType.PlayerDamage, new[] { typeof(IPlayer), typeof(IPlayer), typeof(DamageHandlerBase) } }, - { ServerEventType.PlayerInteractElevator, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerInteractLocker, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerInteractScp330, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerInteractShootingTarget, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerKicked, new[] { typeof(IPlayer), typeof(IPlayer), typeof(string) } }, - { ServerEventType.PlayerMakeNoise, new[] { typeof(IPlayer) } }, - { ServerEventType.PlayerOpenGenerator, new[] { typeof(IPlayer), typeof(Scp079Generator) } }, - { ServerEventType.PlayerPickupAmmo, new[] { typeof(IPlayer), typeof(ItemPickupBase) } }, - { ServerEventType.PlayerPickupArmor, new[] { typeof(IPlayer), typeof(ItemPickupBase) } }, - { ServerEventType.PlayerPickupScp330, new[] { typeof(IPlayer), typeof(ItemPickupBase) } }, - { ServerEventType.PlayerPreauth, new[] { typeof(string), typeof(string), typeof(long), typeof(CentralAuthPreauthFlags), typeof(string), typeof(byte[]) } }, - { ServerEventType.PlayerReceiveEffect, new[] { typeof(IPlayer), typeof(PlayerEffect) } }, - { ServerEventType.PlayerReloadWeapon, new[] { typeof(IPlayer), typeof(Firearm) } }, - { ServerEventType.PlayerChangeRole, new[] { typeof(IPlayer), typeof(PlayerRoleBase), typeof(PlayerRoleBase), typeof(RoleChangeReason) } }, - { ServerEventType.PlayerSearchPickup, new[] { typeof(IPlayer), typeof(ItemPickupBase) } }, - { ServerEventType.PlayerSearchedPickup, new[] { typeof(IPlayer), typeof(ItemPickupBase) } }, - { ServerEventType.PlayerShotWeapon, new[] { typeof(IPlayer), typeof(Firearm) } }, - { ServerEventType.PlayerSpawn, new[] { typeof(IPlayer), typeof(RoleTypeId) } }, - { ServerEventType.RagdollSpawn, new[] { typeof(IPlayer), typeof(IRagdollRole), typeof(DamageHandlerBase) } }, - { ServerEventType.PlayerThrowItem, new[] { typeof(IPlayer), typeof(ItemBase) } }, - { ServerEventType.PlayerToggleFlashlight, new[] { typeof(IPlayer), typeof(ItemBase), typeof(bool) } }, - { ServerEventType.PlayerUnloadWeapon, new[] { typeof(IPlayer), typeof(Firearm) } }, - { ServerEventType.PlayerUnlockGenerator, new[] { typeof(IPlayer), typeof(Scp079Generator) } }, - { ServerEventType.PlayerUsedItem, new[] { typeof(IPlayer), typeof(ItemBase) } }, - { ServerEventType.PlayerUseHotkey, new[] { typeof(IPlayer), typeof(ActionName) } }, - { ServerEventType.PlayerUseItem, new[] { typeof(IPlayer), typeof(UsableItem) } }, - { ServerEventType.PlayerReport, new[] { typeof(IPlayer), typeof(IPlayer), typeof(string) } }, - { ServerEventType.PlayerCheaterReport, new[] { typeof(IPlayer), typeof(IPlayer), typeof(string) } }, - { ServerEventType.RoundEnd, Type.EmptyTypes }, - { ServerEventType.RoundRestart, Type.EmptyTypes }, - { ServerEventType.RoundStart, Type.EmptyTypes }, - { ServerEventType.WaitingForPlayers, Type.EmptyTypes }, - }; + { ServerEventType.PlayerJoined, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerLeft, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerDeath, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "attacker"), + new EventParameter(typeof(DamageHandlerBase), "damageHandler")) }, + { ServerEventType.LczDecontaminationStart, new Event() }, + { ServerEventType.LczDecontaminationAnnouncement, new Event( + new EventParameter(typeof(int), "id")) }, + { ServerEventType.MapGenerated, new Event() }, + { ServerEventType.GrenadeExploded, new Event( + new EventParameter(typeof(ItemPickupBase), "grenade")) }, + { ServerEventType.ItemSpawned, new Event( + new EventParameter(typeof(ItemType), "item")) }, + { ServerEventType.GeneratorActivated, new Event( + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlaceBlood, new Event() }, + { ServerEventType.PlaceBulletHole, new Event() }, + { ServerEventType.PlayerActivateGenerator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlayerAimWeapon, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Firearm), "firearm"), + new EventParameter(typeof(bool), "isAiming")) }, + { ServerEventType.PlayerBanned, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "issuer"), + new EventParameter(typeof(string), "reason"), + new EventParameter(typeof(long), "duration")) }, + { ServerEventType.PlayerCancelUsingItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(UsableItem), "item")) }, + { ServerEventType.PlayerChangeItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ushort), "oldItem"), + new EventParameter(typeof(ushort), "newItem")) }, + { ServerEventType.PlayerChangeRadioRange, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(RadioItem), "radio"), + new EventParameter(typeof(byte), "range")) }, + { ServerEventType.PlayerChangeSpectator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "oldTarget"), + new EventParameter(typeof(IPlayer), "newTarget")) }, + { ServerEventType.PlayerCloseGenerator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlayerDamagedShootingTarget, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ShootingTarget), "shootingTarget"), + new EventParameter(typeof(DamageHandlerBase), "damageHandler"), + new EventParameter(typeof(float), "damageAmount")) }, + { ServerEventType.PlayerDamagedWindow, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(BreakableWindow), "window"), + new EventParameter(typeof(DamageHandlerBase), "damageHandler"), + new EventParameter(typeof(float), "damageAmount")) }, + { ServerEventType.PlayerDeactivatedGenerator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlayerDropAmmo, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemType), "item"), + new EventParameter(typeof(int), "amount")) }, + { ServerEventType.PlayerDropItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemBase), "item")) }, + { ServerEventType.PlayerDryfireWeapon, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Firearm), "firearm")) }, + { ServerEventType.PlayerEscape, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(RoleTypeId), "newRole")) }, + { ServerEventType.PlayerHandcuff, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "target")) }, + { ServerEventType.PlayerRemoveHandcuffs, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "target")) }, + { ServerEventType.PlayerDamage, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "target"), + new EventParameter(typeof(DamageHandlerBase), "damageHandler")) }, + { ServerEventType.PlayerInteractElevator, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerInteractLocker, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerInteractScp330, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerInteractShootingTarget, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerKicked, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "issuer"), + new EventParameter(typeof(string), "reason")) }, + { ServerEventType.PlayerMakeNoise, new Event( + new EventParameter(typeof(IPlayer), "player")) }, + { ServerEventType.PlayerOpenGenerator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlayerPickupAmmo, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemPickupBase), "item")) }, + { ServerEventType.PlayerPickupArmor, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemPickupBase), "item")) }, + { ServerEventType.PlayerPickupScp330, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemPickupBase), "item")) }, + { ServerEventType.PlayerPreauth, new Event( + new EventParameter(typeof(string), "userId"), + new EventParameter(typeof(string), "ipAddress"), + new EventParameter(typeof(long), "expiration"), + new EventParameter(typeof(CentralAuthPreauthFlags), "centralFlags"), + new EventParameter(typeof(string), "region"), + new EventParameter(typeof(byte[]), "signature"), + new EventParameter(typeof(ConnectionRequest), "connectionRequest"), + new EventParameter(typeof(int), "readerStartPosition")) }, + { ServerEventType.PlayerReceiveEffect, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(PlayerEffect), "effect")) }, + { ServerEventType.PlayerReloadWeapon, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Firearm), "firearm")) }, + { ServerEventType.PlayerChangeRole, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(PlayerRoleBase), "oldRole"), + new EventParameter(typeof(PlayerRoleBase), "newRole"), + new EventParameter(typeof(RoleChangeReason), "changeReason")) }, + { ServerEventType.PlayerSearchPickup, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemPickupBase), "item")) }, + { ServerEventType.PlayerSearchedPickup, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemPickupBase), "item")) }, + { ServerEventType.PlayerShotWeapon, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Firearm), "firearm")) }, + { ServerEventType.PlayerSpawn, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(RoleTypeId), "role")) }, + { ServerEventType.RagdollSpawn, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IRagdollRole), "ragdoll"), + new EventParameter(typeof(DamageHandlerBase), "damageHandler")) }, + { ServerEventType.PlayerThrowItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemBase), "item")) }, + { ServerEventType.PlayerToggleFlashlight, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemBase), "item"), + new EventParameter(typeof(bool), "isToggled")) }, + { ServerEventType.PlayerUnloadWeapon, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Firearm), "firearm")) }, + { ServerEventType.PlayerUnlockGenerator, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(Scp079Generator), "generator")) }, + { ServerEventType.PlayerUsedItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ItemBase), "item")) }, + { ServerEventType.PlayerUseHotkey, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(ActionName), "action")) }, + { ServerEventType.PlayerUseItem, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(UsableItem), "item")) }, + { ServerEventType.PlayerReport, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "target"), + new EventParameter(typeof(string), "reason")) }, + { ServerEventType.PlayerCheaterReport, new Event( + new EventParameter(typeof(IPlayer), "player"), + new EventParameter(typeof(IPlayer), "target"), + new EventParameter(typeof(string), "reason")) }, + { ServerEventType.RoundEnd, new Event() }, + { ServerEventType.RoundRestart, new Event() }, + { ServerEventType.RoundStart, new Event() }, + { ServerEventType.WaitingForPlayers, new Event() }, + }; private static bool ValidateEvent(Type[] parameters, Type[] requiredParameters) { @@ -141,28 +253,25 @@ public static void RegisterEvents(object plugin) /// Registers events in type of plugin. /// /// The object of plugin. - public static void RegisterEvents(object plugin) where T : Type => RegisterEvents(plugin.GetType(), typeof(T)); + public static void RegisterEvents(object plugin) where T : Type + { + if (!EventHandlers.TryGetValue(typeof(T), out object handler)) + { + handler = Activator.CreateInstance(typeof(T)); + EventHandlers.Add(typeof(T), handler); + } + + RegisterEvents(plugin.GetType(), handler); + } /// /// Registers events in plugin. /// /// The object of plugin. /// The event handler. - static void RegisterEvents(Type plugin, Type eventHandler) + static void RegisterEvents(Type plugin, object eventHandler) { - if (!EventHandlers.ContainsKey(plugin)) - EventHandlers.Add(plugin, new List()); - - if (!EventHandlers[plugin].Contains(eventHandler)) - EventHandlers[plugin].Add(eventHandler); - - if (!HandlerInstances.TryGetValue(eventHandler, out object handle)) - { - handle = Activator.CreateInstance(eventHandler); - HandlerInstances.Add(eventHandler, handle); - } - - foreach (var method in eventHandler.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + foreach (var method in eventHandler.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var attribute = method.GetCustomAttribute(); @@ -170,23 +279,21 @@ static void RegisterEvents(Type plugin, Type eventHandler) { case PluginEvent pluginEvent: + if (!Events.TryGetValue(pluginEvent.EventType, out Event ev)) + { + Log.Error($"Event &6{pluginEvent.EventType}&r is not registered in manager! ( create issue on github )"); + continue; + } + var eventParameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); - if (!ValidateEvent(eventParameters, RequiredParameters[pluginEvent.EventType])) + if (!ValidateEvent(eventParameters, ev.Parameters.Select(x => x.BaseType).ToArray())) { - Log.Error($"Event &6{method.Name}&r (&6{pluginEvent.EventType}&r) in plugin &6{plugin.FullName}&r contains wrong parameters\n - &6{(string.Join(", ", eventParameters.Select(p => p.Name)))}\n - Required:\n - &6{(string.Join(", ", RequiredParameters[pluginEvent.EventType].Select(p => p.Name)))}."); + Log.Error($"Event &6{method.Name}&r (&6{pluginEvent.EventType}&r) in plugin &6{plugin.FullName}&r contains wrong parameters\n - &6{(string.Join(", ", eventParameters.Select(p => p.Name)))}\n - Required:\n - &6{(string.Join(", ", ev.Parameters.Select(p => p.BaseType.Name)))}."); continue; } - if (!RegisteredEvents.ContainsKey(pluginEvent.EventType)) - RegisteredEvents.Add(pluginEvent.EventType, new List()); - - RegisteredEvents[pluginEvent.EventType].Add(new EventInfo() - { - Plugin = plugin, - Target = handle, - Method = method, - }); + ev.RegisterInvoker(plugin, eventHandler, method); Log.Info($"Registered event &6{method.Name}&r (&6{pluginEvent.EventType}&r) in plugin &6{plugin.FullName}&r!"); break; @@ -194,7 +301,7 @@ static void RegisterEvents(Type plugin, Type eventHandler) } } - private static PlayerFactory GetPlayerFactory(EventInfo ev) + private static PlayerFactory GetPlayerFactory(EventInvokeLocation ev) { if (!FactoryManager.PlayerFactories.TryGetValue(ev.Plugin, out PlayerFactory pFactory)) pFactory = FactoryManager.PlayerFactories[typeof(EventManager)]; @@ -208,23 +315,40 @@ private static PlayerFactory GetPlayerFactory(EventInfo ev) /// The type of event /// The arguments of event. /// If false event is canceled. - public static bool ExecuteEvent(ServerEventType type, params object[] args) + public static bool ExecuteEvent(ServerEventType type, params object[] args) => ExecuteEvent(type, args); + + /// + /// Executes event. + /// + /// The type of event + /// The arguments of event. + /// Event cancellation data. + // ReSharper disable once MemberCanBePrivate.Global + public static T ExecuteEvent(ServerEventType type, params object[] args) where T : struct { - if (!RegisteredEvents.TryGetValue(type, out var registeredEvents)) - return true; + if (!Events.TryGetValue(type, out Event ev)) + { + Log.Error($"Event &6{type}&r is not registered in manager! ( create issue on github )"); + return default; + } + + switch (type) + { + case ServerEventType.PlayerJoined: + if (!(args[0] is IGameComponent component)) break; + + if (!Player.TryGet(component, out Player plr)) break; + + Player.PlayersUserIds.Add(plr.UserId, component); + break; + } var constructEventParameters = new List(); var indexesToRegenerate = new List(); - for (int x = 0; x < RequiredParameters[type].Length; x++) + for (int x = 0; x < ev.Parameters.Length; x++) { - var paramType = RequiredParameters[type][x]; - - if (args[x] == null) - { - constructEventParameters.Add(null); - continue; - } + var paramType = ev.Parameters[x].BaseType; if (paramType == typeof(IPlayer)) { @@ -235,38 +359,55 @@ public static bool ExecuteEvent(ServerEventType type, params object[] args) constructEventParameters.Add(args[x]); } - var isCanceled = false; + bool isBool = typeof(T) == typeof(bool); + bool cancelled = false; + T cancellation = default; - foreach (var ev in registeredEvents) + foreach(var plugin in ev.Invokers.Values) { - foreach (var index in indexesToRegenerate) + foreach(var invoker in plugin) { - if (index.Type == typeof(IPlayer)) + foreach (var index in indexesToRegenerate) { - constructEventParameters[index.Index] = GetPlayerFactory(ev).GetOrAdd((IGameComponent)args[index.Index]); + if (index.Type == typeof(IPlayer)) + constructEventParameters[index.Index] = GetPlayerFactory(invoker).GetOrAdd((IGameComponent)args[index.Index]); } - } - object result; - try - { - result = ev.Method.Invoke(ev.Target, constructEventParameters.ToArray()); - } - catch (Exception ex) - { - Log.Error($"Failed executing event &6{ev.Method.Name}&r (&6{type}&r) in plugin &6{ev.Plugin.FullName}&r\n{ex}"); - continue; - } + object result; + try + { + result = invoker.Invoke(constructEventParameters); + } + catch (Exception ex) + { + Log.Error($"Failed executing event &6{invoker.Method.Name}&r (&6{type}&r) in plugin &6{invoker.Plugin.FullName}&r\n{ex}"); + continue; + } + + if (result is null) + continue; - switch (result) - { - case bool b when !b: - isCanceled = true; - break; + if (isBool) + { + if (result is bool b && b) + cancellation = (T)result; + } + else if (result is T r) + { + if (cancelled || !(r is IEventCancellation ecd) || !ecd.IsCancelled) + continue; + + cancellation = r; + cancelled = true; + } + else + { + Log.Error($"Plugin &6{invoker.Plugin.FullName}&r passed invalid data type for event &6{invoker.Method.Name}&r."); + } } } - - return !isCanceled; + + return cancellation; } } } diff --git a/NwPluginAPI/Events/EventParameter.cs b/NwPluginAPI/Events/EventParameter.cs new file mode 100644 index 0000000..f770d53 --- /dev/null +++ b/NwPluginAPI/Events/EventParameter.cs @@ -0,0 +1,16 @@ +using System; + +namespace PluginAPI.Events +{ + public class EventParameter + { + public Type BaseType { get; } + public string DefaultIdentifierName { get; } + + public EventParameter(Type type,string defaultIdentifierName) + { + BaseType = type; + DefaultIdentifierName = defaultIdentifierName; + } + } +} diff --git a/NwPluginAPI/Events/IEventCancellation.cs b/NwPluginAPI/Events/IEventCancellation.cs new file mode 100644 index 0000000..c3760dc --- /dev/null +++ b/NwPluginAPI/Events/IEventCancellation.cs @@ -0,0 +1,180 @@ +using System; +using LiteNetLib.Utils; + +namespace PluginAPI.Events +{ + public interface IEventCancellation + { + /// + /// Determines whether event is cancelled. + /// + bool IsCancelled { get; } + } + + /// + /// Preauth Event Cancellation Data + /// + public readonly struct PreauthCancellationData : IEventCancellation + { + public bool IsCancelled { get; } + + private readonly bool _handledManually; + + private readonly NetDataWriter _customWriter; + private readonly RejectionReason _reason; + private readonly bool _isForced; + private readonly byte _seconds; + private readonly long _expiration; + private readonly string _customReason; + private readonly ushort _port; + + /// + /// Delays the connection. + /// + /// The delay in seconds. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData RejectDelay(byte seconds, bool isForced) + { + if (seconds < 1 || seconds > 25) + throw new Exception("Delay duration must be between 1 and 25 seconds."); + + return new PreauthCancellationData(RejectionReason.Delay, isForced, seconds: seconds); + } + + /// + /// Rejects the player and redirects them to another server port. + /// + /// The new server port. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData RejectRedirect(byte port, bool isForced) => new PreauthCancellationData(RejectionReason.Redirect, isForced, port: port); + + /// + /// Rejects a player who's trying to authenticate. + /// + /// The ban reason. + /// The ban expiration time. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData RejectBanned(string banReason, DateTime expiration, bool isForced) => + RejectBanned(banReason, expiration.Ticks, isForced); + + /// + /// Rejects a player who's trying to authenticate. + /// + /// The ban reason. + /// The ban expiration time in .NET Ticks. + /// Indicates whether the player has to be rejected forcefully or not. + // ReSharper disable once MemberCanBePrivate.Global + public static PreauthCancellationData RejectBanned(string banReason, long expiration, bool isForced) + { + if (banReason.Length > 400) + throw new ArgumentOutOfRangeException(nameof(banReason), "Reason can't be longer than 400 characters."); + + return new PreauthCancellationData(RejectionReason.Banned, isForced, banReason, expiration: expiration); + } + + /// + /// Rejects a player who's trying to authenticate. + /// + /// Custom rejection reason. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData Reject(string customReason, bool isForced) + { + if (string.IsNullOrEmpty(customReason) || customReason.Length > 400) + throw new ArgumentOutOfRangeException(nameof(customReason), "Reason can't be null, empty or longer than 400 characters."); + + return new PreauthCancellationData(RejectionReason.Custom, isForced, customReason: customReason); + } + + /// + /// Rejects a player who's trying to authenticate. + /// + /// Rejection reason. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData Reject(RejectionReason reason, bool isForced) + { + switch (reason) + { + case RejectionReason.Banned: + case RejectionReason.Delay: + case RejectionReason.Redirect: + case RejectionReason.Custom: + throw new Exception("Specified reason requires extra parameters. Please use the appropriate method."); + + default: + return new PreauthCancellationData(reason, isForced); + } + } + + /// + /// Rejects a player who's trying to authenticate. + /// + /// The instance. + /// Indicates whether the player has to be rejected forcefully or not. + public static PreauthCancellationData Reject(NetDataWriter writer, bool isForced) => new PreauthCancellationData(RejectionReason.NotSpecified, isForced, writer: writer); + + /// + /// Accepts the connection. + /// + public static PreauthCancellationData Accept() => + new PreauthCancellationData(RejectionReason.NotSpecified, false, isCancelled: false); + + public static PreauthCancellationData HandledManually() => new PreauthCancellationData(RejectionReason.NotSpecified, false, handledManually: true); + + private PreauthCancellationData(RejectionReason rejectionReason, bool isForced, string customReason = null, + long expiration = 0, byte seconds = 0, ushort port = 0, NetDataWriter writer = null, bool isCancelled = true, bool handledManually = false) + { + IsCancelled = isCancelled; + + _customWriter = null; + _reason = rejectionReason; + _isForced = isForced; + _customReason = customReason; + _expiration = expiration; + _seconds = seconds; + _port = port; + _customWriter = writer; + _handledManually = handledManually; + } + + /// + /// Generates network writer for the rejection packet. + /// + /// Determines whether the rejection is forced. + /// Network writer + public NetDataWriter GenerateWriter(out bool forced) + { + forced = _isForced; + + if (!IsCancelled || _handledManually) + return null; + + if (_reason == RejectionReason.NotSpecified && _customWriter != null) + return _customWriter; + + var writer = new NetDataWriter(); + writer.Put((byte)_reason); + + switch (_reason) + { + case RejectionReason.Banned: + writer.Put(_expiration); + writer.Put(_customReason); + break; + + case RejectionReason.Custom: + writer.Put(_customReason); + break; + + case RejectionReason.Delay: + writer.Put(_seconds); + break; + + case RejectionReason.Redirect: + writer.Put(_port); + break; + } + + return writer; + } + } +} \ No newline at end of file diff --git a/NwPluginAPI/Loader/AssemblyLoader.cs b/NwPluginAPI/Loader/AssemblyLoader.cs index 0872a17..25186fa 100644 --- a/NwPluginAPI/Loader/AssemblyLoader.cs +++ b/NwPluginAPI/Loader/AssemblyLoader.cs @@ -46,6 +46,10 @@ public static void Initialize() Log.Info("<---< Startup of plugin system... <---<"); Log.Info("Loading of dependencies and plugins in progress..."); IsLoaded = true; + + if (StartupArgs.Args.Any(arg => string.Equals(arg, "-disableAnsiColors", StringComparison.OrdinalIgnoreCase))) + Log.DisableBetterColors = true; + Paths.Setup(); FactoryManager.Init(); diff --git a/TemplatePlugin/MainClass.cs b/TemplatePlugin/MainClass.cs index 98b25c6..877139c 100644 --- a/TemplatePlugin/MainClass.cs +++ b/TemplatePlugin/MainClass.cs @@ -11,6 +11,7 @@ using PlayerStatsSystem; using PluginAPI.Core; using PluginAPI.Core.Attributes; + using PluginAPI.Core.Interfaces; using PluginAPI.Enums; using PluginAPI.Events; using TemplatePlugin.Factory; @@ -54,7 +55,7 @@ void OnPlayerDied(MyPlayer player, MyPlayer attacker, DamageHandlerBase damageHa Log.Info($"Player &6{attacker.Nickname}&r (&6{attacker.UserId}&r) killed &6{player.Nickname}&r (&6{player.UserId}&r), cause {damageHandler}"); } - [PluginEvent(ServerEventType.LczDecontaminationStart)] + [PluginEvent(ServerEventType.LczDecontaminationStart)] void OnLczDecontaminationStarts() { Log.Info("Started LCZ decontamination."); @@ -111,7 +112,7 @@ void OnPlayerActivateGenerator(MyPlayer plr, Scp079Generator gen) [PluginEvent(ServerEventType.PlayerAimWeapon)] void OnPlayerAimsWeapon(MyPlayer plr, Firearm gun, bool isAiming) { - Log.Info($"Player &6{plr.Nickname}&r (&6{plr.UserId}&r) is {(isAiming ? "not aiming" : "aiming")} gun &6{gun.ItemTypeId}&r"); + Log.Info($"Player &6{plr.Nickname}&r (&6{plr.UserId}&r) is {(isAiming ? "aiming" : "not aiming")} gun &6{gun.ItemTypeId}&r"); } [PluginEvent(ServerEventType.PlayerBanned)] diff --git a/TemplatePlugin/TemplatePlugin.csproj b/TemplatePlugin/TemplatePlugin.csproj index 5f478d9..ec73bcc 100644 --- a/TemplatePlugin/TemplatePlugin.csproj +++ b/TemplatePlugin/TemplatePlugin.csproj @@ -18,10 +18,12 @@ + - + + \ No newline at end of file