diff --git a/Obsidian.API/Events/BaseMinecraftEventArgs.cs b/Obsidian.API/Events/BaseMinecraftEventArgs.cs index 26fdac91b..98330e08b 100644 --- a/Obsidian.API/Events/BaseMinecraftEventArgs.cs +++ b/Obsidian.API/Events/BaseMinecraftEventArgs.cs @@ -3,13 +3,17 @@ /// /// Represents the base class for classes that contain minecraft event data. /// -public class BaseMinecraftEventArgs : AsyncEventArgs +public abstract class BaseMinecraftEventArgs : AsyncEventArgs, INamedEvent { + public static string Name => string.Empty; + /// /// Server this event took place in. /// public IServer Server { get; } + public static EventType EventType { get; } + /// /// Constructs a new instance of the class. /// diff --git a/Obsidian.API/Events/BlockBreakEventArgs.cs b/Obsidian.API/Events/BlockBreakEventArgs.cs index 0c11f3ff4..11a6b56bb 100644 --- a/Obsidian.API/Events/BlockBreakEventArgs.cs +++ b/Obsidian.API/Events/BlockBreakEventArgs.cs @@ -2,6 +2,8 @@ public class BlockBreakEventArgs : BlockEventArgs, ICancellable { + public static new string Name => "BlockBreak"; + /// /// Player that has broken the block. /// diff --git a/Obsidian.API/Events/BlockEventArgs.cs b/Obsidian.API/Events/BlockEventArgs.cs index bfdedb7ca..1a91b8bba 100644 --- a/Obsidian.API/Events/BlockEventArgs.cs +++ b/Obsidian.API/Events/BlockEventArgs.cs @@ -2,6 +2,8 @@ public abstract class BlockEventArgs : BaseMinecraftEventArgs { + public static new string Name => "BlockEvent"; + /// /// The impacted block. /// diff --git a/Obsidian.API/Events/ContainerClickEventArgs.cs b/Obsidian.API/Events/ContainerClickEventArgs.cs index 5f017463b..6f817473c 100644 --- a/Obsidian.API/Events/ContainerClickEventArgs.cs +++ b/Obsidian.API/Events/ContainerClickEventArgs.cs @@ -1,9 +1,12 @@ -using System.Diagnostics.CodeAnalysis; +using Obsidian.API.BlockStates; +using System.Diagnostics.CodeAnalysis; namespace Obsidian.API.Events; public sealed class ContainerClickEventArgs : ContainerEventArgs, ICancellable { + public static new string Name => "ContainerClick"; + /// /// Gets the current item that was clicked /// diff --git a/Obsidian.API/Events/ContainerClosedEventArgs.cs b/Obsidian.API/Events/ContainerClosedEventArgs.cs index 75924b67d..22b7d8b0c 100644 --- a/Obsidian.API/Events/ContainerClosedEventArgs.cs +++ b/Obsidian.API/Events/ContainerClosedEventArgs.cs @@ -1,6 +1,8 @@ namespace Obsidian.API.Events; public sealed class ContainerClosedEventArgs : ContainerEventArgs, ICancellable { + public static new string Name => "ContainerClosed"; + public bool IsCancelled { get; private set; } internal ContainerClosedEventArgs(IPlayer player, IServer server) : base(player, server) diff --git a/Obsidian.API/Events/ContainerEventArgs.cs b/Obsidian.API/Events/ContainerEventArgs.cs index 4babfc1ec..7c51ce8ff 100644 --- a/Obsidian.API/Events/ContainerEventArgs.cs +++ b/Obsidian.API/Events/ContainerEventArgs.cs @@ -2,6 +2,8 @@ public class ContainerEventArgs : PlayerEventArgs { + public static new string Name => "ContainerEvent"; + public required BaseContainer Container { get; init; } protected ContainerEventArgs(IPlayer player, IServer server) : base(player, server) diff --git a/Obsidian.API/Events/EntityEventArgs.cs b/Obsidian.API/Events/EntityEventArgs.cs index e810b7a9d..6cd91a2c5 100644 --- a/Obsidian.API/Events/EntityEventArgs.cs +++ b/Obsidian.API/Events/EntityEventArgs.cs @@ -2,6 +2,8 @@ public class EntityEventArgs : BaseMinecraftEventArgs, ICancellable { + public static new string Name => "EntityEvent"; + /// /// The entity involved in this event. /// diff --git a/Obsidian.API/Events/EntityInteractEventArgs.cs b/Obsidian.API/Events/EntityInteractEventArgs.cs index 957d7299e..82370a1ec 100644 --- a/Obsidian.API/Events/EntityInteractEventArgs.cs +++ b/Obsidian.API/Events/EntityInteractEventArgs.cs @@ -2,6 +2,8 @@ public class EntityInteractEventArgs : EntityEventArgs { + public static new string Name => "EntityInteract"; + /// /// The player who interacted with the entity. /// diff --git a/Obsidian.API/Events/IncomingChatMessageEventArgs.cs b/Obsidian.API/Events/IncomingChatMessageEventArgs.cs index 662acd75d..166b3a4f4 100644 --- a/Obsidian.API/Events/IncomingChatMessageEventArgs.cs +++ b/Obsidian.API/Events/IncomingChatMessageEventArgs.cs @@ -2,6 +2,8 @@ public class IncomingChatMessageEventArgs : PlayerEventArgs, ICancellable { + public static new string Name => "IncomingChatMessage"; + /// /// The message that was sent. /// diff --git a/Obsidian.API/Events/PacketReceivedEventArgs.cs b/Obsidian.API/Events/PacketReceivedEventArgs.cs index a92f43d54..d262b6864 100644 --- a/Obsidian.API/Events/PacketReceivedEventArgs.cs +++ b/Obsidian.API/Events/PacketReceivedEventArgs.cs @@ -2,6 +2,8 @@ public sealed class PacketReceivedEventArgs : PlayerEventArgs, ICancellable { + public static new string Name => "PacketReceived"; + /// /// Id of the received packet. /// diff --git a/Obsidian.API/Events/PermissionGrantedEventArgs.cs b/Obsidian.API/Events/PermissionGrantedEventArgs.cs index 7c9f1bf5b..a6d6b0743 100644 --- a/Obsidian.API/Events/PermissionGrantedEventArgs.cs +++ b/Obsidian.API/Events/PermissionGrantedEventArgs.cs @@ -2,6 +2,8 @@ public class PermissionGrantedEventArgs : PlayerEventArgs { + public static new string Name => "PermissionGranted"; + public string Permission { get; } public PermissionGrantedEventArgs(IPlayer player, IServer server, string permission) : base(player, server) diff --git a/Obsidian.API/Events/PermissionRevokedEventArgs.cs b/Obsidian.API/Events/PermissionRevokedEventArgs.cs index 65b00a48e..0f68d0a39 100644 --- a/Obsidian.API/Events/PermissionRevokedEventArgs.cs +++ b/Obsidian.API/Events/PermissionRevokedEventArgs.cs @@ -2,6 +2,8 @@ public class PermissionRevokedEventArgs : PlayerEventArgs { + public static new string Name => "PermissionRevoked"; + public string Permission { get; } public PermissionRevokedEventArgs(IPlayer player, IServer server, string permission) : base(player, server) diff --git a/Obsidian.API/Events/PlayerAttackEntityEventArgs.cs b/Obsidian.API/Events/PlayerAttackEntityEventArgs.cs index ae2539de4..2d5eda423 100644 --- a/Obsidian.API/Events/PlayerAttackEntityEventArgs.cs +++ b/Obsidian.API/Events/PlayerAttackEntityEventArgs.cs @@ -3,6 +3,8 @@ //TODO check if player crits and calculate damage public class PlayerAttackEntityEventArgs : EntityEventArgs { + public static new string Name => "PlayerAttackEntity"; + /// /// The player who interacted with the entity. /// diff --git a/Obsidian.API/Events/PlayerEventArgs.cs b/Obsidian.API/Events/PlayerEventArgs.cs index 9769748f6..aa51476c1 100644 --- a/Obsidian.API/Events/PlayerEventArgs.cs +++ b/Obsidian.API/Events/PlayerEventArgs.cs @@ -2,6 +2,8 @@ public class PlayerEventArgs : BaseMinecraftEventArgs { + public static new string Name => "PlayerEvent"; + /// /// The player involved in this event. /// diff --git a/Obsidian.API/Events/PlayerInteractEventArgs.cs b/Obsidian.API/Events/PlayerInteractEventArgs.cs index 9f30911dd..39b62a148 100644 --- a/Obsidian.API/Events/PlayerInteractEventArgs.cs +++ b/Obsidian.API/Events/PlayerInteractEventArgs.cs @@ -7,6 +7,8 @@ /// public sealed class PlayerInteractEventArgs : PlayerEventArgs, ICancellable { + public static new string Name => "PlayerInteract"; + /// /// The item that was being held when interacting. /// diff --git a/Obsidian.API/Events/PlayerJoinEventArgs.cs b/Obsidian.API/Events/PlayerJoinEventArgs.cs index 49a8b8c81..c672708f2 100644 --- a/Obsidian.API/Events/PlayerJoinEventArgs.cs +++ b/Obsidian.API/Events/PlayerJoinEventArgs.cs @@ -2,6 +2,8 @@ public class PlayerJoinEventArgs : PlayerEventArgs { + public static new string Name => "PlayerJoin"; + /// /// The date the player joined. /// diff --git a/Obsidian.API/Events/PlayerLeaveEventArgs.cs b/Obsidian.API/Events/PlayerLeaveEventArgs.cs index ddcf71c91..6da1a5e77 100644 --- a/Obsidian.API/Events/PlayerLeaveEventArgs.cs +++ b/Obsidian.API/Events/PlayerLeaveEventArgs.cs @@ -2,6 +2,8 @@ public class PlayerLeaveEventArgs : PlayerEventArgs { + public static new string Name => "PlayerLeave"; + /// /// The date the player left. /// diff --git a/Obsidian.API/Events/PlayerTeleportEventArgs.cs b/Obsidian.API/Events/PlayerTeleportEventArgs.cs index 2331e3113..0fbf8b752 100644 --- a/Obsidian.API/Events/PlayerTeleportEventArgs.cs +++ b/Obsidian.API/Events/PlayerTeleportEventArgs.cs @@ -2,6 +2,8 @@ public class PlayerTeleportEventArgs : PlayerEventArgs { + public static new string Name => "PlayerTeleport"; + public VectorF OldPosition { get; } public VectorF NewPosition { get; } diff --git a/Obsidian.API/Events/ServerStatusRequestEventArgs.cs b/Obsidian.API/Events/ServerStatusRequestEventArgs.cs index ce363fea7..16caa22ce 100644 --- a/Obsidian.API/Events/ServerStatusRequestEventArgs.cs +++ b/Obsidian.API/Events/ServerStatusRequestEventArgs.cs @@ -2,6 +2,8 @@ public class ServerStatusRequestEventArgs : BaseMinecraftEventArgs { + public static new string Name => "ServerStatusRequest"; + public IServerStatus Status { get; } internal ServerStatusRequestEventArgs(IServer server, IServerStatus status) : base(server) diff --git a/Obsidian.API/_Enums/EventType.cs b/Obsidian.API/_Enums/EventType.cs new file mode 100644 index 000000000..c329857e3 --- /dev/null +++ b/Obsidian.API/_Enums/EventType.cs @@ -0,0 +1,21 @@ +namespace Obsidian.API; + +public enum EventType +{ + PacketReceived, + QueuePacket, + PlayerJoin, + PlayerLeave, + PlayerTeleported, + PermissionGranted, + PermissionRevoked, + ContainerClick, + BlockBreak, + IncomingChatMessage, + ServerStatusRequest, + EntityInteract, + PlayerAttackEntity, + PlayerInteract, + ContainerClosed, + Custom +} diff --git a/Obsidian.API/_Interfaces/INamedEvent.cs b/Obsidian.API/_Interfaces/INamedEvent.cs new file mode 100644 index 000000000..00e533b20 --- /dev/null +++ b/Obsidian.API/_Interfaces/INamedEvent.cs @@ -0,0 +1,5 @@ +namespace Obsidian.API; +public interface INamedEvent +{ + public static abstract string Name { get; } +} diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index 511b9d80d..7b15f6246 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -317,7 +317,7 @@ public async Task StartConnectionAsync() var configurationPacketReceived = new PacketReceivedEventArgs(Player, this.server, id, data); - await this.server.Events.PacketReceived.InvokeAsync(configurationPacketReceived); + await this.server.packetReceived.InvokeAsync(configurationPacketReceived); if (!configurationPacketReceived.IsCancelled) await this.handler.HandleConfigurationPackets(id, data, this); @@ -328,7 +328,7 @@ public async Task StartConnectionAsync() var playPacketReceived = new PacketReceivedEventArgs(Player, this.server, id, data); - await this.server.Events.PacketReceived.InvokeAsync(playPacketReceived); + await this.server.packetReceived.InvokeAsync(playPacketReceived); if (!playPacketReceived.IsCancelled) await handler.HandlePlayPackets(id, data, this); @@ -345,7 +345,7 @@ public async Task StartConnectionAsync() if (State == ClientState.Play) { Debug.Assert(Player is not null); - await this.server.Events.PlayerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, this.server, DateTimeOffset.Now)); + await this.server.playerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, this.server, DateTimeOffset.Now)); } Disconnected?.Invoke(this); @@ -365,7 +365,7 @@ private async Task HandleServerStatusRequestAsync() { var status = new ServerStatus(this.server); - _ = await this.server.Events.ServerStatusRequest.InvokeAsync(new ServerStatusRequestEventArgs(this.server, status)); + _ = await this.server.serverStatusRequest.InvokeAsync(new ServerStatusRequestEventArgs(this.server, status)); SendPacket(new RequestResponse(status)); } @@ -569,7 +569,7 @@ await QueuePacketAsync(new SynchronizePlayerPositionPacket await Player.UpdateChunksAsync(distance: 7); await SendInfoAsync(); - await this.server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.server, DateTimeOffset.Now)); + await this.server.playerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.server, DateTimeOffset.Now)); } #region Packet sending @@ -741,10 +741,10 @@ internal void SendPacket(IClientboundPacket packet) internal async Task QueuePacketAsync(IClientboundPacket packet) { - var args = await this.server.Events.QueuePacket.InvokeAsync(new QueuePacketEventArgs(this, packet)); + var args = await this.server.queuePacket.InvokeAsync(new QueuePacketEventArgs(this.server, this, packet)); if (args.IsCancelled) { - Logger.LogDebug("Packet {PacketId} was sent to the queue, however an event handler registered in {Name} has cancelled it.", args.Packet.Id, nameof(Server.Events)); + Logger.LogDebug("Packet {PacketId} was sent to the queue, however an event handler has cancelled it.", args.Packet.Id); } else { diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index a02899f59..de8ce1fad 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -225,7 +225,7 @@ public async override Task TeleportAsync(VectorF pos) var tid = Globals.Random.Next(0, 999); - await client.server.Events.PlayerTeleported.InvokeAsync( + await client.server.playerTeleported.InvokeAsync( new PlayerTeleportEventArgs ( this, @@ -786,7 +786,7 @@ public async Task GrantPermissionAsync(string permissionNode) await SavePermsAsync(); if (result) - await this.client.server.Events.PermissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, this.client.server, permissionNode)); + await this.client.server.permissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, this.client.server, permissionNode)); return result; } @@ -808,7 +808,7 @@ public async Task RevokePermissionAsync(string permissionNode) parent.Children.Remove(childToRemove); await this.SavePermsAsync(); - await this.client.server.Events.PermissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, this.client.server, permissionNode)); + await this.client.server.permissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, this.client.server, permissionNode)); return true; } diff --git a/Obsidian/Events/AsyncEvent.T.cs b/Obsidian/Events/AsyncEvent.T.cs index 28e153c80..a1ccea7ef 100644 --- a/Obsidian/Events/AsyncEvent.T.cs +++ b/Obsidian/Events/AsyncEvent.T.cs @@ -1,12 +1,13 @@ -using System.Reflection; +using Obsidian.API.Events; +using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; namespace Obsidian.Events; -public sealed class AsyncEvent : IEventRegistry +public sealed class AsyncEvent where T : BaseMinecraftEventArgs, INamedEvent { - public string? Name { get; } // Name must be set in order to be visible to plugins + public string Name { get; } // Name must be set in order to be visible to plugins private readonly SemaphoreSlim semaphore = new(1); private readonly List> hooks = []; @@ -14,9 +15,16 @@ public sealed class AsyncEvent : IEventRegistry public AsyncEvent() { + this.Name = T.Name; } - public AsyncEvent(string? name, Action, Exception>? exceptionHandler) + public AsyncEvent(Action, Exception>? exceptionHandler) + { + Name = T.Name; + this.exceptionHandler = exceptionHandler; + } + + public AsyncEvent(string name, Action, Exception>? exceptionHandler) { Name = name; this.exceptionHandler = exceptionHandler; diff --git a/Obsidian/Events/EventArgs/BasePacketEventArgs.cs b/Obsidian/Events/EventArgs/BasePacketEventArgs.cs index dc16db6f2..dcd5699bc 100644 --- a/Obsidian/Events/EventArgs/BasePacketEventArgs.cs +++ b/Obsidian/Events/EventArgs/BasePacketEventArgs.cs @@ -3,7 +3,7 @@ namespace Obsidian.Events.EventArgs; -public class BasePacketEventArgs : AsyncEventArgs +public class BasePacketEventArgs : BaseMinecraftEventArgs { /// /// The client that invoked the event. @@ -15,7 +15,7 @@ public class BasePacketEventArgs : AsyncEventArgs /// public IPacket Packet { get; private set; } - internal BasePacketEventArgs(Client client, IPacket packet) + internal BasePacketEventArgs(Server server, Client client, IPacket packet) : base(server) { Client = client; Packet = packet; diff --git a/Obsidian/Events/EventArgs/QueuePacketEventArgs.cs b/Obsidian/Events/EventArgs/QueuePacketEventArgs.cs index 8a3f6d910..713193e9d 100644 --- a/Obsidian/Events/EventArgs/QueuePacketEventArgs.cs +++ b/Obsidian/Events/EventArgs/QueuePacketEventArgs.cs @@ -5,10 +5,12 @@ namespace Obsidian.Events.EventArgs; public class QueuePacketEventArgs : BasePacketEventArgs, ICancellable { + public static new string Name => "QueuePacket"; + /// public bool IsCancelled { get; private set; } - internal QueuePacketEventArgs(Client client, IPacket packet) : base(client, packet) + internal QueuePacketEventArgs(Server server, Client client, IPacket packet) : base(server, client, packet) { } diff --git a/Obsidian/Events/IEventRegistry.cs b/Obsidian/Events/IEventRegistry.cs index 929ce42dc..50e41b6b2 100644 --- a/Obsidian/Events/IEventRegistry.cs +++ b/Obsidian/Events/IEventRegistry.cs @@ -4,7 +4,7 @@ namespace Obsidian.Events; public interface IEventRegistry { - public string? Name { get; } + public string Name { get; } public bool TryRegisterEvent(MethodInfo method, object? instance, out Delegate? @delegate); public bool UnregisterEvent(Delegate @delegate); diff --git a/Obsidian/Events/MinecraftEvent.cs b/Obsidian/Events/MinecraftEvent.cs index fd85dc9d1..c4a7fab5a 100644 --- a/Obsidian/Events/MinecraftEvent.cs +++ b/Obsidian/Events/MinecraftEvent.cs @@ -7,13 +7,15 @@ internal readonly struct MinecraftEvent { public required Type EventType { get; init; } - public required Type ModuleType { get; init; } + public Type? ModuleType { get; init; } public required PluginContainer PluginContainer { get; init; } public required Priority Priority { get; init; } - public required ObjectFactory ModuleFactory { get; init; } + public ObjectFactory? ModuleFactory { get; init; } - public required ObjectMethodExecutor MethodExecutor { get; init; } + public ObjectMethodExecutor? MethodExecutor { get; init; } + + public Delegate? MethodDelegate { get; init; } } diff --git a/Obsidian/Net/Packets/Play/CloseContainerPacket.cs b/Obsidian/Net/Packets/Play/CloseContainerPacket.cs index fe8447e02..dec9b2837 100644 --- a/Obsidian/Net/Packets/Play/CloseContainerPacket.cs +++ b/Obsidian/Net/Packets/Play/CloseContainerPacket.cs @@ -16,6 +16,6 @@ public async ValueTask HandleAsync(Server server, Player player) if (WindowId == 0) return; - await server.Events.ContainerClosed.InvokeAsync(new ContainerClosedEventArgs(player, server) { Container = player.OpenedContainer! }); + await server.containerClosed.InvokeAsync(new ContainerClosedEventArgs(player, server) { Container = player.OpenedContainer! }); } } diff --git a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs index 6a3e7cdcb..02c79a10d 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs @@ -275,7 +275,7 @@ private async Task HandleMouseClick(BaseContainer container, Server server, Play { if (!CarriedItem.IsAir) { - var @event = await server.Events.ContainerClick.InvokeAsync(new ContainerClickEventArgs(player, server, container, CarriedItem) + var @event = await server.containerClick.InvokeAsync(new ContainerClickEventArgs(player, server, container, CarriedItem) { Slot = slot }); diff --git a/Obsidian/Net/Packets/Play/Serverbound/InteractPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/InteractPacket.cs index 5124be933..1c83d7931 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/InteractPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/InteractPacket.cs @@ -30,15 +30,15 @@ public async ValueTask HandleAsync(Server server, Player player) switch (Type) { case InteractionType.Interact: - await server.Events.EntityInteract.InvokeAsync(new EntityInteractEventArgs(player, entity, server, Sneaking)); + await server.entityInteract.InvokeAsync(new EntityInteractEventArgs(player, entity, server, Sneaking)); break; case InteractionType.Attack: - await server.Events.PlayerAttackEntity.InvokeAsync(new PlayerAttackEntityEventArgs(player, entity, server, Sneaking)); + await server.playerAttackEntity.InvokeAsync(new PlayerAttackEntityEventArgs(player, entity, server, Sneaking)); break; case InteractionType.InteractAt: - await server.Events.EntityInteract.InvokeAsync(new EntityInteractEventArgs(player, entity, server, Hand, Target, Sneaking)); + await server.entityInteract.InvokeAsync(new EntityInteractEventArgs(player, entity, server, Hand, Target, Sneaking)); break; } } diff --git a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs index dffc9be25..5c8e9d3da 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs @@ -35,7 +35,7 @@ public async ValueTask HandleAsync(Server server, Player player) SequenceID = Sequence }); - var blockBreakEvent = await server.Events.BlockBreak.InvokeAsync(new BlockBreakEventArgs(server, player, block, Position)); + var blockBreakEvent = await server.blockBreak.InvokeAsync(new BlockBreakEventArgs(server, player, block, Position)); if (blockBreakEvent.Handled) return; } diff --git a/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs index f9eed2e8d..b4721e3bd 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs @@ -41,7 +41,7 @@ public async ValueTask HandleAsync(Server server, Player player) if (TagsRegistry.Blocks.PlayersCanInteract.Entries.Contains(b.RegistryId) && !player.Sneaking) { - await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) + await server.playerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) { Item = currentItem, Block = b, diff --git a/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs index 4e0256c1a..4d5362179 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs @@ -16,7 +16,7 @@ public partial class UseItemPacket : IServerboundPacket public async ValueTask HandleAsync(Server server, Player player) { - await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) + await server.playerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) { Item = this.Hand == Hand.MainHand ? player.GetHeldItem() : player.GetOffHandItem(), Hand = this.Hand, diff --git a/Obsidian/Plugins/PluginManager.cs b/Obsidian/Plugins/PluginManager.cs index a1dd29cc4..4f183187b 100644 --- a/Obsidian/Plugins/PluginManager.cs +++ b/Obsidian/Plugins/PluginManager.cs @@ -7,6 +7,8 @@ using Obsidian.Plugins.PluginProviders; using Obsidian.Plugins.ServiceProviders; using Obsidian.Registries; +using Obsidian.Services; +using System.Collections.Frozen; namespace Obsidian.Plugins; @@ -43,14 +45,15 @@ public sealed class PluginManager public IServiceProvider PluginServiceProvider { get; private set; } = default!; - public PluginManager(IServiceProvider serverProvider, IServer server, ILogger logger) + public PluginManager(IServiceProvider serverProvider, IServer server, + EventDispatcher eventDispatcher, CommandHandler commandHandler, ILogger logger) { var env = serverProvider.GetRequiredService(); this.server = server; this.logger = logger; this.serverProvider = serverProvider; - this.pluginRegistry = new PluginRegistry(server); + this.pluginRegistry = new PluginRegistry(this, eventDispatcher, commandHandler, logger); PluginProviderSelector.RemotePluginProvider = new RemotePluginProvider(logger); PluginProviderSelector.UncompiledPluginProvider = new UncompiledPluginProvider(logger); @@ -113,7 +116,6 @@ private void ConfigureInitialServices(IServerEnvironment env) PluginServiceHandler.InjectServices(this.serverProvider, pluginContainer, this.logger); pluginContainer.Plugin.unload = async () => await UnloadPluginAsync(pluginContainer); - if (pluginContainer.IsReady) { lock (plugins) diff --git a/Obsidian/Plugins/PluginRegistry.cs b/Obsidian/Plugins/PluginRegistry.cs index 07afc5fa3..56056762a 100644 --- a/Obsidian/Plugins/PluginRegistry.cs +++ b/Obsidian/Plugins/PluginRegistry.cs @@ -1,14 +1,17 @@ -using Obsidian.API.Events; +using Microsoft.Extensions.Logging; +using Obsidian.API.Events; using Obsidian.API.Plugins; using Obsidian.Commands.Framework; +using Obsidian.Services; using System.Reflection; namespace Obsidian.Plugins; -public sealed class PluginRegistry(IServer server) : IPluginRegistry +public sealed class PluginRegistry(PluginManager pluginManager, EventDispatcher eventDispatcher, CommandHandler commandHandler, ILogger logger) : IPluginRegistry { - private readonly Server server = (Server)server; - - private CommandHandler CommandHandler => this.server.CommandsHandler; + private readonly PluginManager pluginManager = pluginManager; + private readonly EventDispatcher eventDispatcher = eventDispatcher; + private readonly CommandHandler commandHandler = commandHandler; + private readonly ILogger logger = logger; public IPluginRegistry MapCommand(ContextDelegate contextDelegate) { @@ -18,7 +21,7 @@ public IPluginRegistry MapCommand(ContextDelegate contextDelegat public IPluginRegistry MapCommands() { - var asm = Assembly.GetExecutingAssembly(); + var executingAssembly = Assembly.GetExecutingAssembly(); return this; @@ -26,19 +29,28 @@ public IPluginRegistry MapCommands() public IPluginRegistry MapEvent(ContextDelegate contextDelegate, Priority priority = Priority.Low) where TEventArgs : BaseMinecraftEventArgs { + var executingAssembly = Assembly.GetExecutingAssembly(); + + var pluginContainer = this.pluginManager.Plugins.First(x => x.PluginAssembly == executingAssembly); + this.eventDispatcher.RegisterEvent(pluginContainer, contextDelegate, priority); return this; } public IPluginRegistry MapEvents() { + var executingAssembly = Assembly.GetExecutingAssembly(); + + var pluginContainer = this.pluginManager.Plugins.First(x => x.PluginAssembly == executingAssembly); + + this.eventDispatcher.RegisterEvents(pluginContainer); return this; } public IPluginRegistry RegisterArgumentHandler(T parser) where T : BaseArgumentParser { - this.CommandHandler.AddArgumentParser(parser); + this.commandHandler.AddArgumentParser(parser); return this; } diff --git a/Obsidian/Server.Events.cs b/Obsidian/Server.Events.cs index 2e1565386..abbe1352b 100644 --- a/Obsidian/Server.Events.cs +++ b/Obsidian/Server.Events.cs @@ -13,39 +13,39 @@ namespace Obsidian; public partial class Server { - private AsyncEvent packetReceived = default!; - private AsyncEvent queuePacket = default!; - private AsyncEvent playerJoin = default!; - private AsyncEvent playerLeave = default!; - private AsyncEvent playerTeleported = default!; - private AsyncEvent permissionGranted = default!; - private AsyncEvent permissionRevoked = default!; - private AsyncEvent containerClick = default!; - private AsyncEvent blockBreak = default!; - private AsyncEvent incomingChatMessage = default!; - private AsyncEvent serverStatusRequest = default!; - private AsyncEvent entityInteract = default!; - private AsyncEvent playerAttackEntity = default!; - private AsyncEvent playerInteract = default!; - private AsyncEvent containerClosed = default!; + internal AsyncEvent packetReceived = default!; + internal AsyncEvent queuePacket = default!; + internal AsyncEvent playerJoin = default!; + internal AsyncEvent playerLeave = default!; + internal AsyncEvent playerTeleported = default!; + internal AsyncEvent permissionGranted = default!; + internal AsyncEvent permissionRevoked = default!; + internal AsyncEvent containerClick = default!; + internal AsyncEvent blockBreak = default!; + internal AsyncEvent incomingChatMessage = default!; + internal AsyncEvent serverStatusRequest = default!; + internal AsyncEvent entityInteract = default!; + internal AsyncEvent playerAttackEntity = default!; + internal AsyncEvent playerInteract = default!; + internal AsyncEvent containerClosed = default!; private void InitializeEvents() { - this.packetReceived = new("PacketReceived", this.HandleException); - this.queuePacket = new("QueuePacket", this.HandleException); - this.playerJoin = new("PlayerJoin", this.HandleException); - this.playerLeave = new("PlayerLeave", this.HandleException); - this.playerTeleported = new("PlayerTeleported", this.HandleException); - this.permissionGranted = new("PermissionGranted", this.HandleException); - this.permissionRevoked = new("PermissionRevoked", this.HandleException); - this.containerClick = new("ContainerClick", this.HandleException); - this.blockBreak = new("BlockBreak", this.HandleException); - this.incomingChatMessage = new("IncomingChatMessage", this.HandleException); - this.serverStatusRequest = new("ServerStatusRequest", this.HandleException); - this.entityInteract = new("EntityInteract", this.HandleException); - this.playerAttackEntity = new("PlayerAttackEntity", this.HandleException); - this.playerInteract = new("PlayerInteract", this.HandleException); - this.containerClosed = new("ContainerClosed", this.HandleException); + this.packetReceived = new(this.HandleException); + this.queuePacket = new(this.HandleException); + this.playerJoin = new(this.HandleException); + this.playerLeave = new(this.HandleException); + this.playerTeleported = new(this.HandleException); + this.permissionGranted = new(this.HandleException); + this.permissionRevoked = new(this.HandleException); + this.containerClick = new(this.HandleException); + this.blockBreak = new(this.HandleException); + this.incomingChatMessage = new(this.HandleException); + this.serverStatusRequest = new(this.HandleException); + this.entityInteract = new(this.HandleException); + this.playerAttackEntity = new(this.HandleException); + this.playerInteract = new(this.HandleException); + this.containerClosed = new(this.HandleException); this.playerLeave += OnPlayerLeave; this.playerJoin += OnPlayerJoin; @@ -380,8 +380,8 @@ private async Task OnPlayerJoin(PlayerJoinEventArgs e) } } - private void HandleException(AsyncEvent e, Exception exception) + private void HandleException(AsyncEvent e, Exception exception) where T : BaseMinecraftEventArgs, INamedEvent { - this._logger.LogCritical(exception, "Failed to execute event."); + this._logger.LogCritical(exception, "Failed to execute event {eventName}.", T.Name); } } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index b7012be3a..9cd5ae615 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -62,7 +62,6 @@ public static string VERSION private readonly ILoggerFactory loggerFactory; private readonly RconServer _rconServer; private readonly IUserCache userCache; - private readonly EventDispatcher eventDispatcher; private readonly ILogger _logger; private readonly IServiceProvider serviceProvider; @@ -74,6 +73,7 @@ public static string VERSION public DateTimeOffset StartTime { get; private set; } public PluginManager PluginManager { get; } + public EventDispatcher EventDispatcher { get; } public IOperatorList Operators { get; } public IScoreboardManager ScoreboardManager { get; private set; } @@ -120,9 +120,10 @@ public Server( _logger.LogDebug(message: "Initializing command handler..."); - PluginManager = new PluginManager(this.serviceProvider, this, _logger); CommandsHandler = new CommandHandler(loggerFactory.CreateLogger()); + PluginManager = new PluginManager(this.serviceProvider, this, eventDispatcher, CommandsHandler, _logger); + _logger.LogDebug("Registering commands..."); CommandsHandler.RegisterCommandClass(null); @@ -131,7 +132,7 @@ public Server( _logger.LogDebug("Done registering commands."); this.userCache = playerCache; - this.eventDispatcher = eventDispatcher; + this.EventDispatcher = eventDispatcher; this.loggerFactory = loggerFactory; this.WorldManager = worldManager; diff --git a/Obsidian/Services/EventDispatcher.cs b/Obsidian/Services/EventDispatcher.cs index 49c65eaf4..1a3c27238 100644 --- a/Obsidian/Services/EventDispatcher.cs +++ b/Obsidian/Services/EventDispatcher.cs @@ -2,20 +2,42 @@ using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; using Obsidian.API.Events; +using Obsidian.API.Plugins; using Obsidian.Events; +using Obsidian.Events.EventArgs; using Obsidian.Plugins; +using System.Collections.Frozen; +using System.Diagnostics; using System.Reflection; namespace Obsidian.Services; //TODO BETTER NAME MAYBE?? -public sealed class EventDispatcher(ILogger logger) : IDisposable +public sealed class EventDispatcher : IDisposable { - private static Type eventPriorityAttributeType = typeof(EventPriorityAttribute); - private static Type baseMinecraftEventArgsType = typeof(BaseMinecraftEventArgs); + private static readonly Type eventPriorityAttributeType = typeof(EventPriorityAttribute); + private static readonly Type baseMinecraftEventArgsType = typeof(BaseMinecraftEventArgs); + private static readonly Type minecraftEventHandlerType = typeof(MinecraftEventHandler); - private readonly List registeredEvents = []; - private readonly ILogger logger = logger; + private readonly ILogger logger; + + private readonly FrozenDictionary> registeredEvents; + + public EventDispatcher(ILogger logger) + { + this.logger = logger; + + var events = typeof(INamedEvent).Assembly.GetTypes().Where(x => x.IsAssignableFrom(baseMinecraftEventArgsType)); + var dict = new Dictionary>(); + foreach (var eventType in events) + { + var eventName = eventType.GetProperty("Name")?.GetValue(null)?.ToString() ?? throw new NullReferenceException(); + + dict.Add(eventName, []); + } + + this.registeredEvents = dict.ToFrozenDictionary(); + } public void RegisterEvents(PluginContainer pluginContainer) where TEventModule : MinecraftEventHandler { @@ -27,11 +49,15 @@ public void RegisterEvents(PluginContainer pluginContainer) where { var eventPriorityAttribute = method.GetCustomAttribute()!; var eventType = method.GetParameters().FirstOrDefault()?.ParameterType ?? throw new InvalidOperationException("Method must contain a BaseMinecraftEventArgs type as the first parameter."); + var eventName = eventType.GetProperty("Name")?.GetValue(null)?.ToString() ?? throw new NullReferenceException(); if (!eventType.IsAssignableFrom(baseMinecraftEventArgsType)) throw new InvalidOperationException("Method must contain a BaseMinecraftEventArgs type as the first parameter."); - this.registeredEvents.Add(new() + if (!this.registeredEvents.TryGetValue(eventName, out var values)) + continue; + + values.Add(new() { EventType = eventType, ModuleFactory = ActivatorUtilities.CreateFactory(eventModule, []), @@ -43,39 +69,116 @@ public void RegisterEvents(PluginContainer pluginContainer) where } } - public async ValueTask ExecuteEventAsync(TEventArgs eventArgs) where TEventArgs : BaseMinecraftEventArgs + public void RegisterEvent(PluginContainer pluginContainer, ContextDelegate contextDelegate, Priority priority = Priority.Low) where TEventArgs : BaseMinecraftEventArgs, INamedEvent + { + if (!this.registeredEvents.TryGetValue(TEventArgs.Name, out var values)) + return; + + values.Add(new() + { + EventType = typeof(TEventArgs), + PluginContainer = pluginContainer, + Priority = priority, + MethodDelegate = contextDelegate + }); + + } + + public void RegisterEvents(PluginContainer pluginContainer) { - var events = this.registeredEvents.Where(x => x.EventType == eventArgs.GetType()) - .OrderBy(x => x.Priority);//Plugins with the lowest priority must be called first + var modules = pluginContainer.PluginAssembly.GetTypes().Where(x => x.IsAssignableFrom(minecraftEventHandlerType)); + + foreach (var eventModule in modules) + { + var methods = eventModule.GetMethods().Where(x => x.CustomAttributes.Any(x => x.AttributeType == eventPriorityAttributeType)); + + foreach (var method in methods) + { + var eventPriorityAttribute = method.GetCustomAttribute()!; + var eventType = method.GetParameters().FirstOrDefault()?.ParameterType ?? throw new InvalidOperationException("Method must contain a BaseMinecraftEventArgs type as the first parameter."); + var eventName = eventType.GetProperty("Name")?.GetValue(null)?.ToString() ?? throw new NullReferenceException(); + + if (!eventType.IsAssignableFrom(baseMinecraftEventArgsType)) + throw new InvalidOperationException("Method must contain a BaseMinecraftEventArgs type as the first parameter."); + + if (!this.registeredEvents.TryGetValue(eventName, out var values)) + continue; + + values.Add(new() + { + EventType = eventType, + ModuleFactory = ActivatorUtilities.CreateFactory(eventModule, []), + PluginContainer = pluginContainer, + Priority = eventPriorityAttribute.Priority, + MethodExecutor = ObjectMethodExecutor.Create(method, eventModule.GetTypeInfo()), + ModuleType = eventModule + }); + } + } + } + + public async ValueTask ExecuteEventAsync(TEventArgs eventArgs) where TEventArgs : BaseMinecraftEventArgs, INamedEvent + { + var eventType = eventArgs.GetType(); + + if (!this.registeredEvents.TryGetValue(TEventArgs.Name, out var events)) + return EventResult.Completed; + + var foundEvents = events.OrderBy(x => x.Priority);//Plugins with the lowest priority must be called first var eventResult = EventResult.Completed; - foreach (var @event in events) + foreach (var @event in foundEvents) { - var module = @event.ModuleFactory.Invoke(@event.PluginContainer.ServiceScope.ServiceProvider, null)//Will inject services through constructor + if (@event.ModuleType is not null) + { + var module = @event.ModuleFactory!.Invoke(@event.PluginContainer.ServiceScope.ServiceProvider, null)//Will inject services through constructor ?? throw new InvalidOperationException("Failed to initialize module from factory."); - //inject through attribute - @event.PluginContainer.InjectServices(this.logger, module); + //inject through attribute + @event.PluginContainer.InjectServices(this.logger, module); - try - { - var returnResult = @event.MethodExecutor.Execute(module, new[] { eventArgs });//Maybe have method param service injection?? + try + { + var returnResult = @event.MethodExecutor!.Execute(module, new[] { eventArgs });//Maybe have method param service injection?? - if (returnResult is ValueTask valueTask) - await valueTask; - else if (returnResult is Task task) - await task.ConfigureAwait(false); + if (returnResult is ValueTask valueTask) + await valueTask; + else if (returnResult is Task task) + await task.ConfigureAwait(false); - if (eventArgs is ICancellable cancellable && cancellable.IsCancelled) - eventResult = EventResult.Cancelled; + if (eventArgs is ICancellable cancellable && cancellable.IsCancelled) + eventResult = EventResult.Cancelled; + } + catch (OperationCanceledException) { }//IGNORE this exception + catch (Exception ex) + { + this.logger.LogCritical(ex, "failed to execute event."); + + return EventResult.Failed; + } } - catch (OperationCanceledException) { }//IGNORE this exception - catch (Exception ex) + else { - this.logger.LogCritical(ex, "failed to execute event."); - - return EventResult.Failed; + try + { + var returnResult = @event.MethodDelegate!.DynamicInvoke(eventArgs);//Maybe have method param service injection?? + + if (returnResult is ValueTask valueTask) + await valueTask; + else if (returnResult is Task task) + await task.ConfigureAwait(false); + + if (eventArgs is ICancellable cancellable && cancellable.IsCancelled) + eventResult = EventResult.Cancelled; + } + catch (OperationCanceledException) { }//IGNORE this exception + catch (Exception ex) + { + this.logger.LogCritical(ex, "failed to execute event."); + + return EventResult.Failed; + } } } @@ -84,7 +187,10 @@ public async ValueTask ExecuteEventAsync(TEventArgs eve public void Dispose() { - this.registeredEvents.Clear(); + foreach (var (_, values) in this.registeredEvents) + { + values.Clear(); + } } }