From 9cc31658a17586e6f7861e623c4f78e452f2510e Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:44:57 -0700 Subject: [PATCH] feat: refactor events --- .../patchbukkit/PatchBukkitEventManager.java | 327 ------------------ .../patchbukkit/PatchBukkitPluginManager.java | 5 + .../org/patchbukkit/PatchBukkitServer.java | 8 +- .../patchbukkit/bridge/NativePatchBukkit.java | 75 +++- .../patchbukkit/entity/PatchBukkitPlayer.java | 5 +- .../events/PatchBukkitEventFactory.java | 57 +++ .../events/PatchBukkitEventManager.java | 249 +++++++++++++ .../events/PatchBukkitEventSerializer.java | 22 ++ rust/src/events/handler.rs | 89 +++++ rust/src/events/join.rs | 43 --- rust/src/events/mod.rs | 23 +- rust/src/java/jvm/commands.rs | 10 +- rust/src/java/jvm/worker.rs | 129 ++----- rust/src/java/native_callbacks/events.rs | 130 ++++++- rust/src/java/native_callbacks/mod.rs | 8 + .../mod.rs => command_manager.rs} | 0 rust/src/java/plugin/event_manager.rs | 99 ++++++ rust/src/lib.rs | 12 +- 18 files changed, 760 insertions(+), 531 deletions(-) delete mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java create mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java create mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java create mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java create mode 100644 rust/src/events/handler.rs delete mode 100644 rust/src/events/join.rs rename rust/src/java/plugin/{command_manager/mod.rs => command_manager.rs} (100%) diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java deleted file mode 100644 index 074b51f..0000000 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java +++ /dev/null @@ -1,327 +0,0 @@ -// This file was mostly taken from https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -package org.patchbukkit; - -import co.aikar.timings.TimedEventExecutor; -import com.destroystokyo.paper.event.server.ServerExceptionEvent; -import com.destroystokyo.paper.exception.ServerEventException; -import com.google.common.collect.Sets; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import org.bukkit.Server; -import org.bukkit.Warning; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.plugin.AuthorNagException; -import org.bukkit.plugin.EventExecutor; -import org.bukkit.plugin.IllegalPluginAccessException; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredListener; -import org.jetbrains.annotations.NotNull; - -public class PatchBukkitEventManager { - - private final Server server; - - public PatchBukkitEventManager(Server server) { - this.server = server; - } - - public void callEvent(@NotNull Event event) { - if (event.isAsynchronous() && this.server.isPrimaryThread()) { - throw new IllegalStateException( - event.getEventName() + " may only be triggered asynchronously." - ); - } else if ( - !event.isAsynchronous() && - !this.server.isPrimaryThread() && - !this.server.isStopping() - ) { - throw new IllegalStateException( - event.getEventName() + " may only be triggered synchronously." - ); - } - - HandlerList handlers = event.getHandlers(); - RegisteredListener[] listeners = handlers.getRegisteredListeners(); - - for (RegisteredListener registration : listeners) { - if (!registration.getPlugin().isEnabled()) { - continue; - } - - try { - registration.callEvent(event); - } catch (AuthorNagException ex) { - Plugin plugin = registration.getPlugin(); - - if (plugin.isNaggable()) { - plugin.setNaggable(false); - - this.server.getLogger().log( - Level.SEVERE, - String.format( - "Nag author(s): '%s' of '%s' about the following: %s", - plugin.getPluginMeta().getAuthors(), - plugin.getPluginMeta().getDisplayName(), - ex.getMessage() - ) - ); - } - } catch (Throwable ex) { - String msg = - "Could not pass event " + - event.getEventName() + - " to " + - registration.getPlugin().getPluginMeta().getDisplayName(); - this.server.getLogger().log(Level.SEVERE, msg, ex); - if (!(event instanceof ServerExceptionEvent)) { - // We don't want to cause an endless event loop - this.callEvent( - new ServerExceptionEvent( - new ServerEventException( - msg, - ex, - registration.getPlugin(), - registration.getListener(), - event - ) - ) - ); - } - } - } - } - - public void registerEvents( - @NotNull Listener listener, - @NotNull Plugin plugin - ) { - if (!plugin.isEnabled()) { - throw new IllegalPluginAccessException( - "Plugin attempted to register " + - listener + - " while not enabled" - ); - } - - for (Map.Entry< - Class, - Set - > entry : this.createRegisteredListeners(listener, plugin).entrySet()) { - this.getEventListeners( - this.getRegistrationClass(entry.getKey()) - ).registerAll(entry.getValue()); - } - } - - @NotNull - private HandlerList getEventListeners( - @NotNull Class type - ) { - try { - Method method = this.getRegistrationClass(type).getDeclaredMethod( - "getHandlerList" - ); - method.setAccessible(true); - return (HandlerList) method.invoke(null); - } catch (Exception e) { - throw new IllegalPluginAccessException(e.toString()); - } - } - - @NotNull - private Class getRegistrationClass( - @NotNull Class clazz - ) { - try { - clazz.getDeclaredMethod("getHandlerList"); - return clazz; - } catch (NoSuchMethodException e) { - if ( - clazz.getSuperclass() != null && - !clazz.getSuperclass().equals(Event.class) && - Event.class.isAssignableFrom(clazz.getSuperclass()) - ) { - return this.getRegistrationClass( - clazz.getSuperclass().asSubclass(Event.class) - ); - } else { - throw new IllegalPluginAccessException( - "Unable to find handler list for event " + - clazz.getName() + - ". Static getHandlerList method required!" - ); - } - } - } - - public void registerEvent( - @NotNull Class event, - @NotNull Listener listener, - @NotNull EventPriority priority, - @NotNull EventExecutor executor, - @NotNull Plugin plugin - ) { - this.registerEvent(event, listener, priority, executor, plugin, false); - } - - public void registerEvent( - @NotNull Class event, - @NotNull Listener listener, - @NotNull EventPriority priority, - @NotNull EventExecutor executor, - @NotNull Plugin plugin, - boolean ignoreCancelled - ) { - if (!plugin.isEnabled()) { - throw new IllegalPluginAccessException( - "Plugin attempted to register " + event + " while not enabled" - ); - } - - executor = new TimedEventExecutor(executor, plugin, null, event); - this.getEventListeners(event).register( - new RegisteredListener( - listener, - executor, - priority, - plugin, - ignoreCancelled - ) - ); - } - - @NotNull - public Map< - Class, - Set - > createRegisteredListeners( - @NotNull Listener listener, - @NotNull final Plugin plugin - ) { - Map, Set> ret = - new HashMap<>(); - - Set methods; - try { - Class listenerClazz = listener.getClass(); - methods = Sets.union( - Set.of(listenerClazz.getMethods()), - Set.of(listenerClazz.getDeclaredMethods()) - ); - } catch (NoClassDefFoundError e) { - plugin - .getLogger() - .severe( - "Failed to register events for " + - listener.getClass() + - " because " + - e.getMessage() + - " does not exist." - ); - return ret; - } - - for (final Method method : methods) { - final EventHandler eh = method.getAnnotation(EventHandler.class); - if (eh == null) continue; - // Do not register bridge or synthetic methods to avoid event duplication - // Fixes SPIGOT-893 - if (method.isBridge() || method.isSynthetic()) { - continue; - } - final Class checkClass; - if ( - method.getParameterTypes().length != 1 || - !Event.class.isAssignableFrom( - checkClass = method.getParameterTypes()[0] - ) - ) { - plugin - .getLogger() - .severe( - plugin.getPluginMeta().getDisplayName() + - " attempted to register an invalid EventHandler method signature \"" + - method.toGenericString() + - "\" in " + - listener.getClass() - ); - continue; - } - final Class eventClass = checkClass.asSubclass( - Event.class - ); - method.setAccessible(true); - Set eventSet = ret.computeIfAbsent( - eventClass, - k -> new HashSet<>() - ); - - for ( - Class clazz = eventClass; - Event.class.isAssignableFrom(clazz); - clazz = clazz.getSuperclass() - ) { - // This loop checks for extending deprecated events - if (clazz.getAnnotation(Deprecated.class) != null) { - Warning warning = clazz.getAnnotation(Warning.class); - Warning.WarningState warningState = - this.server.getWarningState(); - if (!warningState.printFor(warning)) { - break; - } - plugin - .getLogger() - .log( - Level.WARNING, - String.format( - "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.", - plugin.getPluginMeta().getDisplayName(), - clazz.getName(), - method.toGenericString(), - (warning != null && - warning.reason().length() != 0) - ? warning.reason() - : "Server performance will be affected", - Arrays.toString( - plugin - .getPluginMeta() - .getAuthors() - .toArray() - ) - ), - warningState == Warning.WarningState.ON - ? new AuthorNagException(null) - : null - ); - break; - } - } - - EventExecutor executor = new TimedEventExecutor( - EventExecutor.create(method, eventClass), - plugin, - method, - eventClass - ); - eventSet.add( - new RegisteredListener( - listener, - executor, - eh.priority(), - plugin, - eh.ignoreCancelled() - ) - ); - } - return ret; - } -} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java index 409d9d9..d90de1b 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java @@ -19,6 +19,7 @@ import org.bukkit.plugin.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; +import org.patchbukkit.events.PatchBukkitEventManager; import org.patchbukkit.permissions.PatchBukkitPermissionManager; @SuppressWarnings("removal") @@ -34,6 +35,10 @@ public class PatchBukkitPluginManager implements PluginManager { this.permissionManager = new PatchBukkitPermissionManager(); } + public PatchBukkitEventManager getEventManager() { + return eventManager; + } + @Override public void registerInterface(@NotNull Class loader) throws IllegalArgumentException {} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java index bbfb6ec..a982154 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java @@ -87,6 +87,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.patchbukkit.command.PatchBukkitCommandMap; import org.patchbukkit.command.PatchBukkitConsoleCommandSender; +import org.patchbukkit.events.PatchBukkitEventManager; import org.patchbukkit.scheduler.PatchBukkitScheduler; @SuppressWarnings("removal") @@ -97,8 +98,7 @@ public class PatchBukkitServer implements Server { private final String bukkitVersion = Versioning.getBukkitVersion(); private final CommandMap commandMap = new PatchBukkitCommandMap(); private final BukkitScheduler scheduler = new PatchBukkitScheduler(); - private final PatchBukkitPluginManager pluginManager = - new PatchBukkitPluginManager(this); + private final PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this); private final Map onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>(); @@ -114,6 +114,10 @@ public class PatchBukkitServer implements Server { this.onlinePlayersByName.put(player.getName().toLowerCase(), player); } + public PatchBukkitEventManager getEventManager() { + return this.pluginManager.getEventManager(); + } + /** * Called from Rust when a player leaves */ diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java index fee9c66..ab478b9 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java @@ -4,8 +4,11 @@ import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.VarHandle; import java.util.UUID; + +import org.bukkit.event.Event; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; +import org.patchbukkit.events.PatchBukkitEventSerializer; public class NativePatchBukkit { @@ -13,6 +16,7 @@ public class NativePatchBukkit { private static MethodHandle sendMessageNative; private static MethodHandle registerEventNative; + private static MethodHandle callEventNative; private static MethodHandle getAbilitiesNative; private static MethodHandle setAbilitiesNative; private static MethodHandle getLocationNative; @@ -183,6 +187,7 @@ public class NativePatchBukkit { public static void initCallbacks( long sendMessageAddr, long registerEventAddr, + long callEventAddr, long getAbilitiesAddr, long setAbilitiesAddr, long getLocationAddr, @@ -198,10 +203,26 @@ public class NativePatchBukkit { FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS) ); - // void rust_register_event(void* listener, void* plugin) + // void rust_register_event(const char* event_type, const char* plugin_name, int32_t priority, bool blocking) registerEventNative = LINKER.downcallHandle( MemorySegment.ofAddress(registerEventAddr), - FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS) + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // event_type string + ValueLayout.ADDRESS, // plugin_name string + ValueLayout.JAVA_INT, // priority ordinal + ValueLayout.JAVA_BOOLEAN // blocking + ) + ); + + // bool rust_call_event(const char* event_type, const char* event_data_json) + // Returns true if Pumpkin handled it, false if unknown event type + callEventNative = LINKER.downcallHandle( + MemorySegment.ofAddress(callEventAddr), + FunctionDescriptor.of( + ValueLayout.JAVA_BOOLEAN, // return: was handled by Pumpkin + ValueLayout.ADDRESS, // event_type string + ValueLayout.ADDRESS // event_data_json string + ) ); // bool rust_get_abilities(const char* uuid, AbilitiesFFI* out) @@ -297,22 +318,24 @@ public class NativePatchBukkit { } /** - * Register an event listener for a plugin. - */ - public static void registerEvent(Listener listener, Plugin plugin) { - // For objects, you have options: - // 1. Pass identifying info (strings, IDs) instead of object refs - // 2. Store objects in a Java-side registry and pass an ID - // 3. Use JNI NewGlobalRef from Rust side if you need to hold refs - // Simple approach - pass plugin name, handle lookup in Rust + * Register an event listener with Pumpkin's event system. + * + * Called by PatchBukkitEventManager when a plugin registers for an event. + * Rust will create a PatchBukkitEventHandler that listens for the corresponding + * Pumpkin event and dispatches back to Java when it fires. + * + * @param eventType Fully qualified Bukkit event class name (e.g. "org.bukkit.event.player.PlayerJoinEvent") + * @param pluginName Name of the plugin registering this listener + * @param priority Bukkit EventPriority ordinal (0=LOWEST through 5=MONITOR), clamped to 0-4 for Pumpkin + * @param blocking Whether the handler should block + */ + public static void registerEvent(String eventType, String pluginName, int priority, boolean blocking) { try (Arena arena = Arena.ofConfined()) { - MemorySegment pluginName = arena.allocateFrom(plugin.getName()); - MemorySegment listenerClass = arena.allocateFrom( - listener.getClass().getName() - ); - registerEventNative.invokeExact(listenerClass, pluginName); + MemorySegment eventTypeStr = arena.allocateFrom(eventType); + MemorySegment pluginNameStr = arena.allocateFrom(pluginName); + registerEventNative.invokeExact(eventTypeStr, pluginNameStr, priority, blocking); } catch (Throwable t) { - throw new RuntimeException("Failed to register event", t); + throw new RuntimeException("Failed to register event: " + eventType + " for plugin " + pluginName, t); } } @@ -543,4 +566,24 @@ public class NativePatchBukkit { throw new RuntimeException("Failed to call native playerPlaySound", t); } } + + public static boolean callEvent(Event event) { + try (Arena arena = Arena.ofConfined()) { + // Serialize event data to JSON for Rust + String eventDataJson = PatchBukkitEventSerializer.serialize(event); + + MemorySegment eventTypeStr = arena.allocateFrom(event.getEventName()); + MemorySegment eventDataStr = arena.allocateFrom(eventDataJson); + + boolean handled = (boolean) callEventNative.invokeExact(eventTypeStr, eventDataStr); + + // If Pumpkin handled it and event is Cancellable, we need to read back + // the cancelled state. This is handled by Rust calling back to update + // the event object directly. + + return handled; + } catch (Throwable t) { + throw new RuntimeException("Failed to call event: " + event.getEventName(), t); + } + } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 40393aa..57f2652 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -59,11 +59,9 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.scoreboard.Scoreboard; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.UnmodifiableView; -import org.patchbukkit.PatchBukkitServer; import org.patchbukkit.bridge.NativePatchBukkit; import org.patchbukkit.registry.PatchBukkitSound; @@ -420,8 +418,7 @@ public class PatchBukkitPlayer @Override public String getDisplayName() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getDisplayName'"); + return this.getName(); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java new file mode 100644 index 0000000..449c0e6 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java @@ -0,0 +1,57 @@ +package org.patchbukkit.events; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.logging.Logger; + +public class PatchBukkitEventFactory { + + private static final Logger LOGGER = Logger.getLogger("PatchBukkit"); + + @Nullable + public static PlayerJoinEvent createPlayerJoinEvent(@NotNull String playerUuid, @NotNull String joinMessage) { + Player player = getPlayer(playerUuid); + if (player == null) return null; + + Component joinMessageComponent = Component.translatable("multiplayer.player.joined", + Component.text(joinMessage)); + + return new PlayerJoinEvent(player, joinMessageComponent); + } + + @Nullable + public static PlayerQuitEvent createPlayerQuitEvent(@NotNull String playerUuid) { + Player player = getPlayer(playerUuid); + if (player == null) return null; + + Component quitMessage = Component.translatable("multiplayer.player.left", + Component.text(player.getName())); + + return new PlayerQuitEvent(player, quitMessage, PlayerQuitEvent.QuitReason.DISCONNECTED); + } + + /** + * Look up a player by UUID string. Returns null with a warning if not found. + */ + @Nullable + private static Player getPlayer(@NotNull String uuidStr) { + try { + UUID uuid = UUID.fromString(uuidStr); + Player player = Bukkit.getServer().getPlayer(uuid); + if (player == null) { + LOGGER.warning("EventFactory: Player not found for UUID " + uuidStr); + } + return player; + } catch (IllegalArgumentException e) { + LOGGER.severe("EventFactory: Invalid UUID string: " + uuidStr); + return null; + } + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java new file mode 100644 index 0000000..a2d4a2b --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java @@ -0,0 +1,249 @@ +package org.patchbukkit.events; + +import org.bukkit.Server; +import org.bukkit.Warning; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.AuthorNagException; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.IllegalPluginAccessException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; +import org.patchbukkit.bridge.NativePatchBukkit; + +import com.google.common.collect.Sets; + +import co.aikar.timings.TimedEventExecutor; + +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +public class PatchBukkitEventManager { + + private final Server server; + + public PatchBukkitEventManager(Server server) { + this.server = server; + } + + public void callEvent(@NotNull Event event) throws IllegalStateException { + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + + boolean handledByPumpkin = NativePatchBukkit.callEvent(event); + + if (!handledByPumpkin) { + // Pumpkin doesn't know this event type, dispatch Java-only + callEventJavaOnly(event); + } + } + + /** + * Java-only event dispatch for events that don't have Pumpkin equivalents. + * Used for custom plugin events or unsupported Bukkit events. + */ + private void callEventJavaOnly(@NotNull Event event) { + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; + } + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + this.server.getLogger().log(Level.SEVERE, String.format( + "Nag author(s): '%s' of '%s' about the following: %s", + plugin.getPluginMeta().getAuthors(), + plugin.getPluginMeta().getDisplayName(), + ex.getMessage() + )); + } + } catch (Throwable ex) { + this.server.getLogger().log( + Level.SEVERE, + "Could not pass event " + event.getEventName() + + " to " + registration.getPlugin().getPluginMeta().getDisplayName(), + ex + ); + } + } + } + + /** + * Called from Rust (via j4rs) when a Pumpkin event fires for a specific plugin. + * + * Iterates PatchBukkitEvent's HandlerList, filters to the target plugin + * by name, and invokes its executors. Cancellation state is set on the + * event and read back by Rust after this returns. + * + * @param event The PatchBukkitEvent populated by Rust + * @param pluginName The plugin whose handlers should execute + */ + public void fireEvent(@NotNull Event event, @NotNull String pluginName) { + for (RegisteredListener listener : event.getHandlers().getRegisteredListeners()) { + if (!listener.getPlugin().getName().equals(pluginName)) continue; + if (!listener.getPlugin().isEnabled()) continue; + + try { + listener.callEvent(event); + } catch (Throwable ex) { + this.server.getLogger().log( + Level.SEVERE, + "Could not pass event " + event.getEventName() + + " to " + listener.getPlugin().getPluginMeta().getDisplayName(), + ex + ); + } + } + } + + public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); + } + + for (Map.Entry, Set> entry : this.createRegisteredListeners(listener, plugin).entrySet()) { + this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); + + for (RegisteredListener rl : entry.getValue()) { + int priorityOrdinal = Math.min(rl.getPriority().ordinal(), 4); + NativePatchBukkit.registerEvent( + entry.getKey().getName(), + plugin.getName(), + priorityOrdinal, + true + ); + } + } + } + + public void registerEvent(@NotNull Class event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin) { + this.registerEvent(event, listener, priority, executor, plugin, false); + } + + public void registerEvent(@NotNull Class event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + + executor = new TimedEventExecutor(executor, plugin, null, event); + this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + + int priorityOrdinal = Math.min(priority.ordinal(), 4); + NativePatchBukkit.registerEvent(event.getName(), plugin.getName(), priorityOrdinal, true); // We default to having all events blocking. + + } + + @NotNull + private HandlerList getEventListeners(@NotNull Class type) { + try { + Method method = this.getRegistrationClass(type).getDeclaredMethod("getHandlerList"); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception e) { + throw new IllegalPluginAccessException(e.toString()); + } + } + + @NotNull + private Class getRegistrationClass(@NotNull Class clazz) { + try { + clazz.getDeclaredMethod("getHandlerList"); + return clazz; + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null + && !clazz.getSuperclass().equals(Event.class) + && Event.class.isAssignableFrom(clazz.getSuperclass())) { + return this.getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); + } else { + throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); + } + } + } + + @NotNull + public Map, Set> createRegisteredListeners(@NotNull Listener listener, @NotNull final Plugin plugin) { + Map, Set> ret = new HashMap<>(); + + Set methods; + try { + Class listenerClazz = listener.getClass(); + methods = Sets.union( + Set.of(listenerClazz.getMethods()), + Set.of(listenerClazz.getDeclaredMethods()) + ); + } catch (NoClassDefFoundError e) { + plugin.getLogger().severe("Failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist."); + return ret; + } + + for (final Method method : methods) { + final EventHandler eh = method.getAnnotation(EventHandler.class); + if (eh == null) continue; + // Do not register bridge or synthetic methods to avoid event duplication + // Fixes SPIGOT-893 + if (method.isBridge() || method.isSynthetic()) { + continue; + } + final Class checkClass; + if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) { + plugin.getLogger().severe(plugin.getPluginMeta().getDisplayName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass()); + continue; + } + final Class eventClass = checkClass.asSubclass(Event.class); + method.setAccessible(true); + Set eventSet = ret.computeIfAbsent(eventClass, k -> new HashSet<>()); + + for (Class clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { + // This loop checks for extending deprecated events + if (clazz.getAnnotation(Deprecated.class) != null) { + Warning warning = clazz.getAnnotation(Warning.class); + Warning.WarningState warningState = this.server.getWarningState(); + if (!warningState.printFor(warning)) { + break; + } + plugin.getLogger().log( + Level.WARNING, + String.format( + "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.", + plugin.getPluginMeta().getDisplayName(), + clazz.getName(), + method.toGenericString(), + (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected", + Arrays.toString(plugin.getPluginMeta().getAuthors().toArray())), + warningState == Warning.WarningState.ON ? new AuthorNagException(null) : null); + break; + } + } + + EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); + eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } + return ret; + } + + public void clearEvents() { + HandlerList.unregisterAll(); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java new file mode 100644 index 0000000..e1c3fc6 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java @@ -0,0 +1,22 @@ +package org.patchbukkit.events; + +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerJoinEvent; + +import com.google.gson.JsonObject; + +public class PatchBukkitEventSerializer { + public static String serialize(Event event) { + JsonObject json = new JsonObject(); + + if (event instanceof PlayerJoinEvent joinEvent) { + var joinMessage = joinEvent.joinMessage(); + if (joinMessage != null) { + json.addProperty("playerUuid", joinEvent.getPlayer().getUniqueId().toString()); + json.addProperty("joinMessage", joinMessage.toString()); + } + } + + return json.toString(); + } +} diff --git a/rust/src/events/handler.rs b/rust/src/events/handler.rs new file mode 100644 index 0000000..e2886c0 --- /dev/null +++ b/rust/src/events/handler.rs @@ -0,0 +1,89 @@ +use std::marker::PhantomData; +use std::sync::Arc; + +use pumpkin::entity::player::Player; +use pumpkin::plugin::{BoxFuture, EventHandler, Payload}; +use pumpkin::server::Server; +use pumpkin_api_macros::with_runtime; +use tokio::sync::{mpsc, oneshot}; + +use crate::java::jvm::commands::JvmCommand; + +#[derive(Clone)] +pub enum PatchBukkitEvent { + PlayerJoinEvent { + server: Arc, + player: Arc, + join_message: String, + }, +} + +pub trait IntoEventData { + fn into_patch_bukkit_event(&self, server: Arc) -> PatchBukkitEvent; +} + +impl IntoEventData for pumpkin::plugin::player::player_join::PlayerJoinEvent { + fn into_patch_bukkit_event(&self, server: Arc) -> PatchBukkitEvent { + PatchBukkitEvent::PlayerJoinEvent { + server: server, + player: self.player.clone(), + join_message: self.join_message.clone().get_text(), + } + } +} + +pub struct PatchBukkitEventHandler { + plugin_name: String, + command_tx: mpsc::Sender, + _phantom: PhantomData, +} + +impl PatchBukkitEventHandler { + pub fn new(plugin_name: String, command_tx: mpsc::Sender) -> Self { + Self { + plugin_name, + command_tx, + _phantom: PhantomData, + } + } +} + +#[with_runtime(global)] +impl EventHandler for PatchBukkitEventHandler +where + E: IntoEventData + Payload + 'static, +{ + fn handle_blocking<'a>( + &'a self, + server: &'a Arc, + event: &'a mut E, + ) -> BoxFuture<'a, ()> { + let command_tx = self.command_tx.clone(); + + Box::pin(async move { + let (tx, rx) = oneshot::channel(); + if let Err(e) = command_tx + .send(JvmCommand::FireEvent { + patchbukkit_event: event.into_patch_bukkit_event(server.clone()), + respond_to: tx, + plugin: self.plugin_name.clone(), + }) + .await + { + log::error!("Failed to send event to JVM worker: {}", e); + return; + } + + match rx.await { + Ok(cancelled) => { + if cancelled { + log::debug!("Event was cancelled by a Java plugin"); + } + } + Err(_) => { + log::warn!("JVM worker dropped response channel for event"); + } + } + }) + } +} diff --git a/rust/src/events/join.rs b/rust/src/events/join.rs deleted file mode 100644 index dcfe704..0000000 --- a/rust/src/events/join.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::sync::Arc; - -use pumpkin::{ - plugin::{BoxFuture, EventHandler, player::player_join::PlayerJoinEvent}, - server::Server, -}; -use pumpkin_api_macros::with_runtime; -use tokio::sync::{mpsc, oneshot}; - -use crate::{events::Event, java::jvm::commands::JvmCommand}; - -pub struct PatchBukkitJoinHandler { - pub command_tx: mpsc::Sender, -} - -#[with_runtime(global)] -impl EventHandler for PatchBukkitJoinHandler { - fn handle_blocking<'a>( - &self, - _server: &Arc, - event: &'a mut PlayerJoinEvent, - ) -> BoxFuture<'a, ()> { - let command_tx = self.command_tx.clone(); - Box::pin(async move { - let event: &mut PlayerJoinEvent = event; - - let (tx, _rx) = oneshot::channel(); - let sent_event = event.clone(); - command_tx - .send(JvmCommand::TriggerEvent { - event: Event::PlayerJoinEvent(sent_event), - respond_to: tx, - }) - .await - .unwrap(); - - // match rx.await.unwrap().unwrap() { - // Event::PlayerJoinEvent(player_join_event) => *event = player_join_event, - // _ => unreachable!(), - // }; - }) - } -} diff --git a/rust/src/events/mod.rs b/rust/src/events/mod.rs index a660124..062ae9d 100644 --- a/rust/src/events/mod.rs +++ b/rust/src/events/mod.rs @@ -1,22 +1 @@ -use std::sync::Arc; - -use pumpkin::plugin::{Context, EventPriority, player::player_join::PlayerJoinEvent}; -use tokio::sync::mpsc; - -use crate::{events::join::PatchBukkitJoinHandler, java::jvm::commands::JvmCommand}; - -pub mod join; - -pub enum Event { - PlayerJoinEvent(PlayerJoinEvent), -} - -pub async fn register_handlers(command_tx: mpsc::Sender, server: &Arc) { - server - .register_event( - Arc::new(PatchBukkitJoinHandler { command_tx }), - EventPriority::Highest, - true, - ) - .await; -} +pub mod handler; diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index de26c8a..913bce9 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -6,7 +6,7 @@ use pumpkin_protocol::java::client::play::CommandSuggestion; use tokio::sync::{mpsc, oneshot}; use uuid::Uuid; -use crate::{commands::SimpleCommandSender, events::Event}; +use crate::{commands::SimpleCommandSender, events::handler::PatchBukkitEvent}; pub enum LoadPluginResult { SuccessfullyLoadedSpigot, @@ -22,6 +22,7 @@ pub enum JvmCommand { j4rs_path: PathBuf, respond_to: oneshot::Sender>, context: Arc, + command_tx: mpsc::Sender, }, LoadPlugin { plugin_path: PathBuf, @@ -41,9 +42,10 @@ pub enum JvmCommand { Shutdown { respond_to: oneshot::Sender>, }, - TriggerEvent { - event: Event, - respond_to: oneshot::Sender>, + FireEvent { + patchbukkit_event: PatchBukkitEvent, + plugin: String, + respond_to: oneshot::Sender, // true = cancelled }, TriggerCommand { full_command: String, diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 2430acf..94f83ff 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -4,15 +4,12 @@ use j4rs::{InvocationArg, Jvm, JvmBuilder}; use pumpkin::plugin::Context; use tokio::sync::mpsc; -use crate::{ - events::Event, - java::{ - jar::read_configs_from_jar, - jvm::commands::{JvmCommand, LoadPluginResult}, - native_callbacks::{init_callback_context, initialize_callbacks}, - plugin::{ - command_manager::CommandManager, event_manager::EventManager, manager::PluginManager, - }, +use crate::java::{ + jar::read_configs_from_jar, + jvm::commands::{JvmCommand, LoadPluginResult}, + native_callbacks::{init_callback_context, initialize_callbacks}, + plugin::{ + command_manager::CommandManager, event_manager::EventManager, manager::PluginManager, }, }; @@ -46,9 +43,14 @@ impl JvmWorker { j4rs_path, respond_to, context, + command_tx, } => { - init_callback_context(context.clone(), tokio::runtime::Handle::current()) - .unwrap(); + init_callback_context( + context.clone(), + tokio::runtime::Handle::current(), + command_tx.clone(), + ) + .unwrap(); self.context = Some(context); let result = self.initialize_jvm(&j4rs_path); let _ = respond_to.send(result); @@ -132,102 +134,29 @@ impl JvmWorker { let _ = respond_to.send(self.plugin_manager.unload_all_plugins()); break; } - JvmCommand::TriggerEvent { - event, - respond_to: _, + JvmCommand::FireEvent { + respond_to, + plugin, + patchbukkit_event, } => { let jvm = match self.jvm { Some(ref jvm) => jvm, None => &Jvm::attach_thread().unwrap(), }; - match event { - Event::PlayerJoinEvent(player_join_event) => { - let server_instance = jvm - .invoke_static( - "org.bukkit.Bukkit", - "getServer", - InvocationArg::empty(), - ) - .map_err(|e| format!("Failed to get server: {}", e)) - .unwrap(); + let cancelled = + match self + .event_manager + .fire_event(jvm, patchbukkit_event, plugin) + { + Ok(c) => c, + Err(e) => { + log::error!("Failed to fire event: {}", e); + false + } + }; - // let cloned_server_instance = - // jvm.clone_instance(&server_instance).unwrap(); - - // let j_entity = jvm - // .create_instance( - // "org.patchbukkit.entity.PatchBukkitEntity", - // &[ - // InvocationArg::from(cloned_server_instance), - // // InvocationArg::from(InvocationArg::empty()), // Placeholder for the actual NMS entity - // ], - // ) - // .unwrap(); - // let cloned_server_instance = - // jvm.clone_instance(&server_instance).unwrap(); - - let player = player_join_event.player; - - let j_uuid = jvm - .invoke_static( - "java.util.UUID", - "fromString", - &[InvocationArg::try_from(player.gameprofile.id.to_string()) - .unwrap()], - ) - .map_err(|e| format!("Failed to create Java UUID: {}", e)) - .unwrap(); - - let j_player = jvm - .create_instance( - "org.patchbukkit.entity.PatchBukkitPlayer", - &[ - InvocationArg::from(j_uuid), - InvocationArg::try_from(player.gameprofile.name.clone()) - .unwrap(), - ], - ) - .map_err(|e| format!("Failed to create player instance: {}", e)) - .unwrap(); - - let player_permission_level = player.permission_lvl.load(); - if player_permission_level - >= self - .context - .as_ref() - .unwrap() - .server - .basic_config - .op_permission_level - { - jvm.invoke( - &j_player, - "setOp", - &[InvocationArg::try_from(true) - .unwrap() - .into_primitive() - .unwrap()], - ) - .map_err(|e| { - format!("Failed to give operator status to new player: {}", e) - }) - .unwrap(); - }; - - let patch_server = jvm - .cast(&server_instance, "org.patchbukkit.PatchBukkitServer") - .unwrap(); - - jvm.invoke( - &patch_server, - "registerPlayer", - &[InvocationArg::from(j_player)], - ) - .unwrap(); - // self.event_manager.call_event(&jvm, event) - } - } + let _ = respond_to.send(cancelled); } JvmCommand::TriggerCommand { full_command, diff --git a/rust/src/java/native_callbacks/events.rs b/rust/src/java/native_callbacks/events.rs index 494aa98..342a1b7 100644 --- a/rust/src/java/native_callbacks/events.rs +++ b/rust/src/java/native_callbacks/events.rs @@ -1,9 +1,129 @@ -use std::ffi::c_void; +use std::ffi::c_char; +use std::sync::Arc; + +use pumpkin::plugin::EventPriority; +use pumpkin::plugin::player::player_join::PlayerJoinEvent; +use pumpkin_util::text::TextComponent; + +use crate::events::handler::PatchBukkitEventHandler; +use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; -#[unsafe(no_mangle)] pub extern "C" fn rust_register_event( - _listener_ptr: *const c_void, // opaque pointer to Java object - _plugin_ptr: *const c_void, + event_type_ptr: *const c_char, + plugin_name_ptr: *const c_char, + priority: i32, + blocking: bool, ) { - // Handle event registration + let event_type = get_string(event_type_ptr); + let plugin_name = get_string(plugin_name_ptr); + + let Some(ctx) = CALLBACK_CONTEXT.get() else { + log::error!("CallbackContext not initialized when registering event"); + return; + }; + + let pumpkin_priority = match priority { + 0 => EventPriority::Lowest, + 1 => EventPriority::Low, + 2 => EventPriority::Normal, + 3 => EventPriority::High, + _ => EventPriority::Highest, + }; + + log::info!( + "Plugin '{}' registering listener for '{}' (priority={:?}, blocking={})", + plugin_name, + event_type, + priority, + blocking + ); + + let command_tx = ctx.command_tx.clone(); + let context = ctx.plugin_context.clone(); + let event_type_owned = event_type.clone(); + + tokio::task::block_in_place(|| { + ctx.runtime.block_on(async { + match event_type_owned.as_str() { + "org.bukkit.event.player.PlayerJoinEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_join::PlayerJoinEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + blocking, + ) + .await; + } + _ => { + log::warn!( + "Unsupported Bukkit event type '{}' from plugin '{}'", + event_type_owned, plugin_name + ); + } + } + }); + }); +} + +pub extern "C" fn rust_call_event( + event_type_ptr: *const c_char, + event_data_ptr: *const c_char, +) -> bool { + let event_type = get_string(event_type_ptr); + let event_data_json = get_string(event_data_ptr); + + let Some(ctx) = CALLBACK_CONTEXT.get() else { + log::error!("CallbackContext not initialized when calling event"); + return false; + }; + + log::debug!( + "Java calling event '{}' with data: {}", + event_type, + event_data_json + ); + + let event_data: serde_json::Value = match serde_json::from_str(&event_data_json) { + Ok(v) => v, + Err(e) => { + log::error!("Failed to parse event data JSON: {}", e); + return false; + } + }; + + let context = ctx.plugin_context.clone(); + + let handled = tokio::task::block_in_place(|| { + ctx.runtime.block_on(async { + match event_type.as_str() { + "org.bukkit.event.player.PlayerJoinEvent" => { + let player_uuid_str = event_data["playerUuid"].as_str().unwrap_or(""); + let join_message_str = event_data["joinMessage"].as_str().unwrap_or(""); + if let Ok(uuid) = uuid::Uuid::parse_str(player_uuid_str) { + if let Some(player) = context.server.get_player_by_uuid(uuid) { + let pumpkin_event = PlayerJoinEvent::new( + player, + TextComponent::from_legacy_string(join_message_str), + ); + context.server.plugin_manager.fire(pumpkin_event).await; + return true; + } + } + false + } + _ => { + log::warn!("Unknown event type for Pumpkin: {}", event_type); + false + } + } + }) + }); + + handled } diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index 70e3592..ae0b3e7 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -3,6 +3,9 @@ use std::sync::{Arc, OnceLock}; use anyhow::Result; use j4rs::{InvocationArg, Jvm}; use pumpkin::plugin::Context; +use tokio::sync::mpsc; + +use crate::java::jvm::commands::JvmCommand; pub mod abilities; pub mod events; @@ -19,15 +22,18 @@ static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); struct CallbackContext { pub plugin_context: Arc, pub runtime: tokio::runtime::Handle, + pub command_tx: mpsc::Sender, } pub fn init_callback_context( plugin_context: Arc, runtime: tokio::runtime::Handle, + command_tx: mpsc::Sender, ) -> Result<()> { let context = CallbackContext { plugin_context, runtime, + command_tx, }; CALLBACK_CONTEXT @@ -39,6 +45,7 @@ pub fn init_callback_context( pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { let send_message_addr = message::rust_send_message as *const () as i64; let register_event_addr = events::rust_register_event as *const () as i64; + let call_event_addr = events::rust_call_event as *const () as i64; let get_abilities_addr = abilities::rust_get_abilities as *const () as i64; let set_abilities_addr = abilities::rust_set_abilities as *const () as i64; let get_location_addr = location::rust_get_location as *const () as i64; @@ -55,6 +62,7 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { &[ InvocationArg::try_from(send_message_addr)?.into_primitive()?, InvocationArg::try_from(register_event_addr)?.into_primitive()?, + InvocationArg::try_from(call_event_addr)?.into_primitive()?, InvocationArg::try_from(get_abilities_addr)?.into_primitive()?, InvocationArg::try_from(set_abilities_addr)?.into_primitive()?, InvocationArg::try_from(get_location_addr)?.into_primitive()?, diff --git a/rust/src/java/plugin/command_manager/mod.rs b/rust/src/java/plugin/command_manager.rs similarity index 100% rename from rust/src/java/plugin/command_manager/mod.rs rename to rust/src/java/plugin/command_manager.rs diff --git a/rust/src/java/plugin/event_manager.rs b/rust/src/java/plugin/event_manager.rs index 1699290..4ff8fca 100644 --- a/rust/src/java/plugin/event_manager.rs +++ b/rust/src/java/plugin/event_manager.rs @@ -1,5 +1,10 @@ +use std::sync::Arc; + use anyhow::Result; use j4rs::{Instance, InvocationArg, Jvm}; +use pumpkin::{entity::player::Player, server::Server}; + +use crate::events::handler::PatchBukkitEvent; pub struct EventManager {} @@ -8,6 +13,100 @@ impl EventManager { Self {} } + pub fn fire_event( + &self, + jvm: &Jvm, + event: PatchBukkitEvent, + plugin_name: String, + ) -> Result { + let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?; + let patch_server = jvm.cast(&server, "org.patchbukkit.PatchBukkitServer")?; + let event_manager = jvm.invoke(&patch_server, "getEventManager", InvocationArg::empty())?; + + let j_event = match event { + PatchBukkitEvent::PlayerJoinEvent { + server, + player, + join_message, + } => { + EventManager::register_player(jvm, &patch_server, &player, &server)?; + jvm.invoke_static( + "org.patchbukkit.events.PatchBukkitEventFactory", + "createPlayerJoinEvent", + &[ + InvocationArg::try_from(player.gameprofile.id.to_string())?, + InvocationArg::try_from(join_message)?, + ], + )? + } + }; + + let j_event_for_fire = jvm.clone_instance(&j_event)?; + jvm.invoke( + &event_manager, + "fireEvent", + &[ + InvocationArg::from(j_event_for_fire), + InvocationArg::try_from(plugin_name)?, + ], + )?; + + let cancelled = match jvm.invoke( + &jvm.clone_instance(&j_event)?, + "isCancelled", + InvocationArg::empty(), + ) { + Ok(instance) => jvm.to_rust::(instance).unwrap_or(false), + Err(_) => false, + }; + + Ok(cancelled) + } + + pub fn register_player( + jvm: &Jvm, + patch_server: &Instance, + player: &Arc, + server: &Arc, + ) -> Result<()> { + let j_uuid = jvm + .invoke_static( + "java.util.UUID", + "fromString", + &[InvocationArg::try_from(player.gameprofile.id.to_string())?], + ) + .map_err(|e| format!("Failed to create Java UUID: {}", e)) + .unwrap(); + + let j_player = jvm.create_instance( + "org.patchbukkit.entity.PatchBukkitPlayer", + &[ + InvocationArg::from(j_uuid), + InvocationArg::try_from(player.gameprofile.name.clone())?, + ], + )?; + + let player_permission_level = player.permission_lvl.load(); + if player_permission_level >= server.basic_config.op_permission_level { + jvm.invoke( + &j_player, + "setOp", + &[InvocationArg::try_from(true) + .unwrap() + .into_primitive() + .unwrap()], + )?; + }; + + jvm.invoke( + &patch_server, + "registerPlayer", + &[InvocationArg::from(j_player)], + )?; + + Ok(()) + } + pub fn call_event(&self, jvm: &Jvm, event: Instance) -> Result<()> { let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?; let plugin_manager = jvm.invoke(&server, "getPluginManager", InvocationArg::empty())?; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index f05bbae..bbfcda1 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -19,20 +19,15 @@ use tokio::sync::{ oneshot, }; -use crate::{ - events::register_handlers, - java::jvm::{ - commands::{JvmCommand, LoadPluginResult}, - worker::JvmWorker, - }, +use crate::java::jvm::{ + commands::{JvmCommand, LoadPluginResult}, + worker::JvmWorker, }; async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc) -> Result<(), String> { server.init_log(); log::info!("Starting PatchBukkit"); - register_handlers(plugin.command_tx.clone(), &server).await; - // Setup directories let dirs = setup_directories(&server)?; @@ -112,6 +107,7 @@ async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc) -> j4rs_path: dirs.j4rs, respond_to: tx, context: server.clone(), + command_tx: plugin.command_tx.clone(), }) .await .map_err(|e| format!("Failed to send command to initialize J4RS: {}", e))?;