feat: refactor events
This commit is contained in:
parent
5ce6f50f99
commit
9cc31658a1
@ -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<? extends Event>,
|
|
||||||
Set<RegisteredListener>
|
|
||||||
> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
|
|
||||||
this.getEventListeners(
|
|
||||||
this.getRegistrationClass(entry.getKey())
|
|
||||||
).registerAll(entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private HandlerList getEventListeners(
|
|
||||||
@NotNull Class<? extends Event> 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<? extends Event> getRegistrationClass(
|
|
||||||
@NotNull Class<? extends Event> 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<? extends Event> 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<? extends Event> 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<? extends Event>,
|
|
||||||
Set<RegisteredListener>
|
|
||||||
> createRegisteredListeners(
|
|
||||||
@NotNull Listener listener,
|
|
||||||
@NotNull final Plugin plugin
|
|
||||||
) {
|
|
||||||
Map<Class<? extends Event>, Set<RegisteredListener>> ret =
|
|
||||||
new HashMap<>();
|
|
||||||
|
|
||||||
Set<Method> 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<? extends Event> eventClass = checkClass.asSubclass(
|
|
||||||
Event.class
|
|
||||||
);
|
|
||||||
method.setAccessible(true);
|
|
||||||
Set<RegisteredListener> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -19,6 +19,7 @@ import org.bukkit.plugin.*;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
|
import org.patchbukkit.events.PatchBukkitEventManager;
|
||||||
import org.patchbukkit.permissions.PatchBukkitPermissionManager;
|
import org.patchbukkit.permissions.PatchBukkitPermissionManager;
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
@ -34,6 +35,10 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
this.permissionManager = new PatchBukkitPermissionManager();
|
this.permissionManager = new PatchBukkitPermissionManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatchBukkitEventManager getEventManager() {
|
||||||
|
return eventManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerInterface(@NotNull Class<? extends PluginLoader> loader)
|
public void registerInterface(@NotNull Class<? extends PluginLoader> loader)
|
||||||
throws IllegalArgumentException {}
|
throws IllegalArgumentException {}
|
||||||
|
|||||||
@ -87,6 +87,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.patchbukkit.command.PatchBukkitCommandMap;
|
import org.patchbukkit.command.PatchBukkitCommandMap;
|
||||||
import org.patchbukkit.command.PatchBukkitConsoleCommandSender;
|
import org.patchbukkit.command.PatchBukkitConsoleCommandSender;
|
||||||
|
import org.patchbukkit.events.PatchBukkitEventManager;
|
||||||
import org.patchbukkit.scheduler.PatchBukkitScheduler;
|
import org.patchbukkit.scheduler.PatchBukkitScheduler;
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
@ -97,8 +98,7 @@ public class PatchBukkitServer implements Server {
|
|||||||
private final String bukkitVersion = Versioning.getBukkitVersion();
|
private final String bukkitVersion = Versioning.getBukkitVersion();
|
||||||
private final CommandMap commandMap = new PatchBukkitCommandMap();
|
private final CommandMap commandMap = new PatchBukkitCommandMap();
|
||||||
private final BukkitScheduler scheduler = new PatchBukkitScheduler();
|
private final BukkitScheduler scheduler = new PatchBukkitScheduler();
|
||||||
private final PatchBukkitPluginManager pluginManager =
|
private final PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this);
|
||||||
new PatchBukkitPluginManager(this);
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<UUID, Player> onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
private final Map<UUID, Player> onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
@ -114,6 +114,10 @@ public class PatchBukkitServer implements Server {
|
|||||||
this.onlinePlayersByName.put(player.getName().toLowerCase(), player);
|
this.onlinePlayersByName.put(player.getName().toLowerCase(), player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatchBukkitEventManager getEventManager() {
|
||||||
|
return this.pluginManager.getEventManager();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from Rust when a player leaves
|
* Called from Rust when a player leaves
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4,8 +4,11 @@ import java.lang.foreign.*;
|
|||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.VarHandle;
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.event.Event;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.patchbukkit.events.PatchBukkitEventSerializer;
|
||||||
|
|
||||||
public class NativePatchBukkit {
|
public class NativePatchBukkit {
|
||||||
|
|
||||||
@ -13,6 +16,7 @@ public class NativePatchBukkit {
|
|||||||
|
|
||||||
private static MethodHandle sendMessageNative;
|
private static MethodHandle sendMessageNative;
|
||||||
private static MethodHandle registerEventNative;
|
private static MethodHandle registerEventNative;
|
||||||
|
private static MethodHandle callEventNative;
|
||||||
private static MethodHandle getAbilitiesNative;
|
private static MethodHandle getAbilitiesNative;
|
||||||
private static MethodHandle setAbilitiesNative;
|
private static MethodHandle setAbilitiesNative;
|
||||||
private static MethodHandle getLocationNative;
|
private static MethodHandle getLocationNative;
|
||||||
@ -183,6 +187,7 @@ public class NativePatchBukkit {
|
|||||||
public static void initCallbacks(
|
public static void initCallbacks(
|
||||||
long sendMessageAddr,
|
long sendMessageAddr,
|
||||||
long registerEventAddr,
|
long registerEventAddr,
|
||||||
|
long callEventAddr,
|
||||||
long getAbilitiesAddr,
|
long getAbilitiesAddr,
|
||||||
long setAbilitiesAddr,
|
long setAbilitiesAddr,
|
||||||
long getLocationAddr,
|
long getLocationAddr,
|
||||||
@ -198,10 +203,26 @@ public class NativePatchBukkit {
|
|||||||
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)
|
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(
|
registerEventNative = LINKER.downcallHandle(
|
||||||
MemorySegment.ofAddress(registerEventAddr),
|
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)
|
// bool rust_get_abilities(const char* uuid, AbilitiesFFI* out)
|
||||||
@ -297,22 +318,24 @@ public class NativePatchBukkit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an event listener for a plugin.
|
* 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(Listener listener, Plugin plugin) {
|
public static void registerEvent(String eventType, String pluginName, int priority, boolean blocking) {
|
||||||
// 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
|
|
||||||
try (Arena arena = Arena.ofConfined()) {
|
try (Arena arena = Arena.ofConfined()) {
|
||||||
MemorySegment pluginName = arena.allocateFrom(plugin.getName());
|
MemorySegment eventTypeStr = arena.allocateFrom(eventType);
|
||||||
MemorySegment listenerClass = arena.allocateFrom(
|
MemorySegment pluginNameStr = arena.allocateFrom(pluginName);
|
||||||
listener.getClass().getName()
|
registerEventNative.invokeExact(eventTypeStr, pluginNameStr, priority, blocking);
|
||||||
);
|
|
||||||
registerEventNative.invokeExact(listenerClass, pluginName);
|
|
||||||
} catch (Throwable t) {
|
} 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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,11 +59,9 @@ import org.bukkit.potion.PotionEffect;
|
|||||||
import org.bukkit.potion.PotionEffectType;
|
import org.bukkit.potion.PotionEffectType;
|
||||||
import org.bukkit.scoreboard.Scoreboard;
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
import org.jetbrains.annotations.UnmodifiableView;
|
import org.jetbrains.annotations.UnmodifiableView;
|
||||||
import org.patchbukkit.PatchBukkitServer;
|
|
||||||
import org.patchbukkit.bridge.NativePatchBukkit;
|
import org.patchbukkit.bridge.NativePatchBukkit;
|
||||||
import org.patchbukkit.registry.PatchBukkitSound;
|
import org.patchbukkit.registry.PatchBukkitSound;
|
||||||
|
|
||||||
@ -420,8 +418,7 @@ public class PatchBukkitPlayer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
// TODO Auto-generated method stub
|
return this.getName();
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getDisplayName'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Class<? extends Event>, Set<RegisteredListener>> 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<? extends Event> 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<? extends Event> 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<? extends Event> 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<? extends Event> getRegistrationClass(@NotNull Class<? extends Event> 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<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(@NotNull Listener listener, @NotNull final Plugin plugin) {
|
||||||
|
Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<>();
|
||||||
|
|
||||||
|
Set<Method> 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<? extends Event> eventClass = checkClass.asSubclass(Event.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
Set<RegisteredListener> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
89
rust/src/events/handler.rs
Normal file
89
rust/src/events/handler.rs
Normal file
@ -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<Server>,
|
||||||
|
player: Arc<Player>,
|
||||||
|
join_message: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoEventData {
|
||||||
|
fn into_patch_bukkit_event(&self, server: Arc<Server>) -> PatchBukkitEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoEventData for pumpkin::plugin::player::player_join::PlayerJoinEvent {
|
||||||
|
fn into_patch_bukkit_event(&self, server: Arc<Server>) -> PatchBukkitEvent {
|
||||||
|
PatchBukkitEvent::PlayerJoinEvent {
|
||||||
|
server: server,
|
||||||
|
player: self.player.clone(),
|
||||||
|
join_message: self.join_message.clone().get_text(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PatchBukkitEventHandler<E: IntoEventData> {
|
||||||
|
plugin_name: String,
|
||||||
|
command_tx: mpsc::Sender<JvmCommand>,
|
||||||
|
_phantom: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: IntoEventData> PatchBukkitEventHandler<E> {
|
||||||
|
pub fn new(plugin_name: String, command_tx: mpsc::Sender<JvmCommand>) -> Self {
|
||||||
|
Self {
|
||||||
|
plugin_name,
|
||||||
|
command_tx,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[with_runtime(global)]
|
||||||
|
impl<E> EventHandler<E> for PatchBukkitEventHandler<E>
|
||||||
|
where
|
||||||
|
E: IntoEventData + Payload + 'static,
|
||||||
|
{
|
||||||
|
fn handle_blocking<'a>(
|
||||||
|
&'a self,
|
||||||
|
server: &'a Arc<Server>,
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<JvmCommand>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[with_runtime(global)]
|
|
||||||
impl EventHandler<PlayerJoinEvent> for PatchBukkitJoinHandler {
|
|
||||||
fn handle_blocking<'a>(
|
|
||||||
&self,
|
|
||||||
_server: &Arc<Server>,
|
|
||||||
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!(),
|
|
||||||
// };
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +1 @@
|
|||||||
use std::sync::Arc;
|
pub mod handler;
|
||||||
|
|
||||||
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<JvmCommand>, server: &Arc<Context>) {
|
|
||||||
server
|
|
||||||
.register_event(
|
|
||||||
Arc::new(PatchBukkitJoinHandler { command_tx }),
|
|
||||||
EventPriority::Highest,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use pumpkin_protocol::java::client::play::CommandSuggestion;
|
|||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{commands::SimpleCommandSender, events::Event};
|
use crate::{commands::SimpleCommandSender, events::handler::PatchBukkitEvent};
|
||||||
|
|
||||||
pub enum LoadPluginResult {
|
pub enum LoadPluginResult {
|
||||||
SuccessfullyLoadedSpigot,
|
SuccessfullyLoadedSpigot,
|
||||||
@ -22,6 +22,7 @@ pub enum JvmCommand {
|
|||||||
j4rs_path: PathBuf,
|
j4rs_path: PathBuf,
|
||||||
respond_to: oneshot::Sender<Result<()>>,
|
respond_to: oneshot::Sender<Result<()>>,
|
||||||
context: Arc<Context>,
|
context: Arc<Context>,
|
||||||
|
command_tx: mpsc::Sender<JvmCommand>,
|
||||||
},
|
},
|
||||||
LoadPlugin {
|
LoadPlugin {
|
||||||
plugin_path: PathBuf,
|
plugin_path: PathBuf,
|
||||||
@ -41,9 +42,10 @@ pub enum JvmCommand {
|
|||||||
Shutdown {
|
Shutdown {
|
||||||
respond_to: oneshot::Sender<Result<()>>,
|
respond_to: oneshot::Sender<Result<()>>,
|
||||||
},
|
},
|
||||||
TriggerEvent {
|
FireEvent {
|
||||||
event: Event,
|
patchbukkit_event: PatchBukkitEvent,
|
||||||
respond_to: oneshot::Sender<Result<Event>>,
|
plugin: String,
|
||||||
|
respond_to: oneshot::Sender<bool>, // true = cancelled
|
||||||
},
|
},
|
||||||
TriggerCommand {
|
TriggerCommand {
|
||||||
full_command: String,
|
full_command: String,
|
||||||
|
|||||||
@ -4,16 +4,13 @@ use j4rs::{InvocationArg, Jvm, JvmBuilder};
|
|||||||
use pumpkin::plugin::Context;
|
use pumpkin::plugin::Context;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{
|
use crate::java::{
|
||||||
events::Event,
|
|
||||||
java::{
|
|
||||||
jar::read_configs_from_jar,
|
jar::read_configs_from_jar,
|
||||||
jvm::commands::{JvmCommand, LoadPluginResult},
|
jvm::commands::{JvmCommand, LoadPluginResult},
|
||||||
native_callbacks::{init_callback_context, initialize_callbacks},
|
native_callbacks::{init_callback_context, initialize_callbacks},
|
||||||
plugin::{
|
plugin::{
|
||||||
command_manager::CommandManager, event_manager::EventManager, manager::PluginManager,
|
command_manager::CommandManager, event_manager::EventManager, manager::PluginManager,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct JvmWorker {
|
pub struct JvmWorker {
|
||||||
@ -46,8 +43,13 @@ impl JvmWorker {
|
|||||||
j4rs_path,
|
j4rs_path,
|
||||||
respond_to,
|
respond_to,
|
||||||
context,
|
context,
|
||||||
|
command_tx,
|
||||||
} => {
|
} => {
|
||||||
init_callback_context(context.clone(), tokio::runtime::Handle::current())
|
init_callback_context(
|
||||||
|
context.clone(),
|
||||||
|
tokio::runtime::Handle::current(),
|
||||||
|
command_tx.clone(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.context = Some(context);
|
self.context = Some(context);
|
||||||
let result = self.initialize_jvm(&j4rs_path);
|
let result = self.initialize_jvm(&j4rs_path);
|
||||||
@ -132,102 +134,29 @@ impl JvmWorker {
|
|||||||
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JvmCommand::TriggerEvent {
|
JvmCommand::FireEvent {
|
||||||
event,
|
respond_to,
|
||||||
respond_to: _,
|
plugin,
|
||||||
|
patchbukkit_event,
|
||||||
} => {
|
} => {
|
||||||
let jvm = match self.jvm {
|
let jvm = match self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
Some(ref jvm) => jvm,
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
None => &Jvm::attach_thread().unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
let cancelled =
|
||||||
Event::PlayerJoinEvent(player_join_event) => {
|
match self
|
||||||
let server_instance = jvm
|
.event_manager
|
||||||
.invoke_static(
|
.fire_event(jvm, patchbukkit_event, plugin)
|
||||||
"org.bukkit.Bukkit",
|
|
||||||
"getServer",
|
|
||||||
InvocationArg::empty(),
|
|
||||||
)
|
|
||||||
.map_err(|e| format!("Failed to get server: {}", e))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// 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(
|
Ok(c) => c,
|
||||||
&j_player,
|
Err(e) => {
|
||||||
"setOp",
|
log::error!("Failed to fire event: {}", e);
|
||||||
&[InvocationArg::try_from(true)
|
false
|
||||||
.unwrap()
|
}
|
||||||
.into_primitive()
|
|
||||||
.unwrap()],
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
format!("Failed to give operator status to new player: {}", e)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_server = jvm
|
let _ = respond_to.send(cancelled);
|
||||||
.cast(&server_instance, "org.patchbukkit.PatchBukkitServer")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
jvm.invoke(
|
|
||||||
&patch_server,
|
|
||||||
"registerPlayer",
|
|
||||||
&[InvocationArg::from(j_player)],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// self.event_manager.call_event(&jvm, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
JvmCommand::TriggerCommand {
|
JvmCommand::TriggerCommand {
|
||||||
full_command,
|
full_command,
|
||||||
|
|||||||
@ -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(
|
pub extern "C" fn rust_register_event(
|
||||||
_listener_ptr: *const c_void, // opaque pointer to Java object
|
event_type_ptr: *const c_char,
|
||||||
_plugin_ptr: *const c_void,
|
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<pumpkin::plugin::player::player_join::PlayerJoinEvent>,
|
||||||
|
>(
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ use std::sync::{Arc, OnceLock};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::{InvocationArg, Jvm};
|
use j4rs::{InvocationArg, Jvm};
|
||||||
use pumpkin::plugin::Context;
|
use pumpkin::plugin::Context;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::java::jvm::commands::JvmCommand;
|
||||||
|
|
||||||
pub mod abilities;
|
pub mod abilities;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
@ -19,15 +22,18 @@ static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new();
|
|||||||
struct CallbackContext {
|
struct CallbackContext {
|
||||||
pub plugin_context: Arc<Context>,
|
pub plugin_context: Arc<Context>,
|
||||||
pub runtime: tokio::runtime::Handle,
|
pub runtime: tokio::runtime::Handle,
|
||||||
|
pub command_tx: mpsc::Sender<JvmCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_callback_context(
|
pub fn init_callback_context(
|
||||||
plugin_context: Arc<Context>,
|
plugin_context: Arc<Context>,
|
||||||
runtime: tokio::runtime::Handle,
|
runtime: tokio::runtime::Handle,
|
||||||
|
command_tx: mpsc::Sender<JvmCommand>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let context = CallbackContext {
|
let context = CallbackContext {
|
||||||
plugin_context,
|
plugin_context,
|
||||||
runtime,
|
runtime,
|
||||||
|
command_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
CALLBACK_CONTEXT
|
CALLBACK_CONTEXT
|
||||||
@ -39,6 +45,7 @@ pub fn init_callback_context(
|
|||||||
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
||||||
let send_message_addr = message::rust_send_message as *const () as i64;
|
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 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 get_abilities_addr = abilities::rust_get_abilities as *const () as i64;
|
||||||
let set_abilities_addr = abilities::rust_set_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;
|
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(send_message_addr)?.into_primitive()?,
|
||||||
InvocationArg::try_from(register_event_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(get_abilities_addr)?.into_primitive()?,
|
||||||
InvocationArg::try_from(set_abilities_addr)?.into_primitive()?,
|
InvocationArg::try_from(set_abilities_addr)?.into_primitive()?,
|
||||||
InvocationArg::try_from(get_location_addr)?.into_primitive()?,
|
InvocationArg::try_from(get_location_addr)?.into_primitive()?,
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm};
|
use j4rs::{Instance, InvocationArg, Jvm};
|
||||||
|
use pumpkin::{entity::player::Player, server::Server};
|
||||||
|
|
||||||
|
use crate::events::handler::PatchBukkitEvent;
|
||||||
|
|
||||||
pub struct EventManager {}
|
pub struct EventManager {}
|
||||||
|
|
||||||
@ -8,6 +13,100 @@ impl EventManager {
|
|||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fire_event(
|
||||||
|
&self,
|
||||||
|
jvm: &Jvm,
|
||||||
|
event: PatchBukkitEvent,
|
||||||
|
plugin_name: String,
|
||||||
|
) -> Result<bool> {
|
||||||
|
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::<bool>(instance).unwrap_or(false),
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(cancelled)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_player(
|
||||||
|
jvm: &Jvm,
|
||||||
|
patch_server: &Instance,
|
||||||
|
player: &Arc<Player>,
|
||||||
|
server: &Arc<Server>,
|
||||||
|
) -> 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<()> {
|
pub fn call_event(&self, jvm: &Jvm, event: Instance) -> Result<()> {
|
||||||
let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?;
|
let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?;
|
||||||
let plugin_manager = jvm.invoke(&server, "getPluginManager", InvocationArg::empty())?;
|
let plugin_manager = jvm.invoke(&server, "getPluginManager", InvocationArg::empty())?;
|
||||||
|
|||||||
@ -19,20 +19,15 @@ use tokio::sync::{
|
|||||||
oneshot,
|
oneshot,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::java::jvm::{
|
||||||
events::register_handlers,
|
|
||||||
java::jvm::{
|
|
||||||
commands::{JvmCommand, LoadPluginResult},
|
commands::{JvmCommand, LoadPluginResult},
|
||||||
worker::JvmWorker,
|
worker::JvmWorker,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
||||||
server.init_log();
|
server.init_log();
|
||||||
log::info!("Starting PatchBukkit");
|
log::info!("Starting PatchBukkit");
|
||||||
|
|
||||||
register_handlers(plugin.command_tx.clone(), &server).await;
|
|
||||||
|
|
||||||
// Setup directories
|
// Setup directories
|
||||||
let dirs = setup_directories(&server)?;
|
let dirs = setup_directories(&server)?;
|
||||||
|
|
||||||
@ -112,6 +107,7 @@ async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc<Context>) ->
|
|||||||
j4rs_path: dirs.j4rs,
|
j4rs_path: dirs.j4rs,
|
||||||
respond_to: tx,
|
respond_to: tx,
|
||||||
context: server.clone(),
|
context: server.clone(),
|
||||||
|
command_tx: plugin.command_tx.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to send command to initialize J4RS: {}", e))?;
|
.map_err(|e| format!("Failed to send command to initialize J4RS: {}", e))?;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user