many fixes
This commit is contained in:
parent
e1fb278af0
commit
43e65ca412
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
# Rust resources
|
# Rust resources
|
||||||
rust/resources/jassets/*
|
rust/resources/jassets/*
|
||||||
!rust/resources/jassets/j4rs-*
|
|
||||||
|
|
||||||
rust/resources/deps/*
|
|
||||||
|
|
||||||
# Gradle cache
|
# Gradle cache
|
||||||
.gradle/
|
.gradle/
|
||||||
|
|||||||
@ -47,9 +47,8 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
paperweight.paperDevBundle("26.2.build.111-stable")
|
paperweight.paperDevBundle("26.2.build.112-stable")
|
||||||
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
||||||
implementation("io.github.astonbitecode:j4rs:0.25.2")
|
|
||||||
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
||||||
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
||||||
implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
|
implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
|
||||||
|
|||||||
@ -42,6 +42,8 @@ public class SimpleCommandMap implements CommandMap {
|
|||||||
knownCommands.put(label, command);
|
knownCommands.put(label, command);
|
||||||
registeredDirectly = true;
|
registeredDirectly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
command.register(this);
|
||||||
knownCommands.put(fallbackPrefix + ":" + label, command);
|
knownCommands.put(fallbackPrefix + ":" + label, command);
|
||||||
|
|
||||||
if (command.getAliases() != null) {
|
if (command.getAliases() != null) {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
|||||||
JavaPlugin plugin,
|
JavaPlugin plugin,
|
||||||
BooleanSupplier registrationCheck
|
BooleanSupplier registrationCheck
|
||||||
) {
|
) {
|
||||||
return null;
|
return new org.patchbukkit.events.PatchBukkitLifecycleEventManager(plugin, registrationCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStatisticCriteriaKey(Statistic statistic) {
|
public String getStatisticCriteriaKey(Statistic statistic) {
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.plugin.EventExecutor;
|
import org.bukkit.plugin.EventExecutor;
|
||||||
@ -24,6 +26,8 @@ import org.patchbukkit.PatchBukkitPluginManager;
|
|||||||
@SuppressWarnings({ "deprecation", "removal" })
|
@SuppressWarnings({ "deprecation", "removal" })
|
||||||
public class PatchBukkitPluginLoader implements PluginLoader {
|
public class PatchBukkitPluginLoader implements PluginLoader {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("PatchBukkitPluginLoader");
|
||||||
|
|
||||||
public static Plugin createPlugin(
|
public static Plugin createPlugin(
|
||||||
String jarPath,
|
String jarPath,
|
||||||
String mainClass,
|
String mainClass,
|
||||||
@ -33,7 +37,7 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
|||||||
try {
|
try {
|
||||||
File jarFile = new File(jarPath);
|
File jarFile = new File(jarPath);
|
||||||
if (!jarFile.exists()) {
|
if (!jarFile.exists()) {
|
||||||
System.err.println(
|
LOGGER.severe(
|
||||||
"[PatchBukkit] Plugin file does not exist: " + jarPath
|
"[PatchBukkit] Plugin file does not exist: " + jarPath
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
@ -87,16 +91,17 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
|||||||
pm.registerPlugin(plugin);
|
pm.registerPlugin(plugin);
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
|
try {
|
||||||
|
PatchBukkitBootstrap.registerPluginCommands(plugin, classLoader.getDescription());
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
try {
|
try {
|
||||||
plugin.onLoad();
|
plugin.onLoad();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Error during onLoad() for " + mainClass + ": " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + mainClass, t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
return plugin;
|
return plugin;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to instantiate plugin class " + mainClass, e);
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,11 +115,11 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
|
||||||
System.err.println("[PatchBukkit] Failed to enable plugin " + pluginName + ": " + t.getMessage());
|
|
||||||
t.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to enable plugin " + pluginName, t);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean disablePlugin(String pluginName) {
|
public static boolean disablePlugin(String pluginName) {
|
||||||
@ -126,11 +131,11 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
|
||||||
System.err.println("[PatchBukkit] Failed to disable plugin " + pluginName + ": " + t.getMessage());
|
|
||||||
t.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to disable plugin " + pluginName, t);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -107,11 +107,13 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
Class<? extends Plugin> pluginClass = jarClass.asSubclass(Plugin.class);
|
Class<? extends Plugin> pluginClass = jarClass.asSubclass(Plugin.class);
|
||||||
Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
|
Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
|
||||||
loader.init((org.bukkit.plugin.java.JavaPlugin) plugin);
|
loader.init((org.bukkit.plugin.java.JavaPlugin) plugin);
|
||||||
|
try {
|
||||||
|
org.patchbukkit.loader.PatchBukkitBootstrap.registerPluginCommands(plugin, description);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
try {
|
try {
|
||||||
plugin.onLoad();
|
plugin.onLoad();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
server.getLogger().severe("Error loading " + plugin.getName() + ": " + t.getMessage());
|
server.getLogger().log(Level.SEVERE, "Error loading " + plugin.getName(), t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
registerPlugin(plugin);
|
registerPlugin(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
@ -145,7 +147,7 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
result.add(plugin);
|
result.add(plugin);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
server.getLogger().severe("Could not load plugin '" + file.getName() + "' in folder '" + file.getParent() + "': " + e.getMessage());
|
server.getLogger().log(Level.SEVERE, "Could not load plugin '" + file.getName() + "' in folder '" + file.getParent() + "'", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,15 @@ import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
|
|||||||
import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
|
import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -175,6 +181,10 @@ public class PatchBukkitServer implements Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final PrintStream ORIGINAL_OUT = System.out;
|
||||||
|
private static final PrintStream ORIGINAL_ERR = System.err;
|
||||||
|
private static final ThreadLocal<Boolean> IN_LOGGING = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger("Minecraft");
|
private static final Logger logger = Logger.getLogger("Minecraft");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -201,20 +211,136 @@ public class PatchBukkitServer implements Server {
|
|||||||
root.addHandler(new java.util.logging.Handler() {
|
root.addHandler(new java.util.logging.Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void publish(java.util.logging.LogRecord record) {
|
public void publish(java.util.logging.LogRecord record) {
|
||||||
|
if (record == null) return;
|
||||||
|
if (IN_LOGGING.get()) return;
|
||||||
|
IN_LOGGING.set(true);
|
||||||
try {
|
try {
|
||||||
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
||||||
|
String message = formatLogRecord(record);
|
||||||
|
String loggerName = record.getLoggerName() != null ? record.getLoggerName() : "";
|
||||||
|
if (loggerName.startsWith("jdk.") || loggerName.startsWith("sun.") || loggerName.startsWith("java.") || loggerName.startsWith("javax.")) {
|
||||||
|
if (record.getLevel().intValue() < Level.WARNING.intValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
NativeBridgeFfi.sendLog(
|
NativeBridgeFfi.sendLog(
|
||||||
SendLogRequest.newBuilder()
|
SendLogRequest.newBuilder()
|
||||||
.setLevel(logLevel)
|
.setLevel(logLevel)
|
||||||
.setMessage(record.getMessage() != null ? record.getMessage() : "")
|
.setMessage(message)
|
||||||
.setLoggerName(record.getLoggerName() != null ? record.getLoggerName() : "")
|
.setLoggerName(loggerName)
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable t) {
|
||||||
|
ORIGINAL_ERR.println("[" + record.getLevel() + "][" + loggerName + "] " + message);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ORIGINAL_ERR.println("[RootLogger Error] Failed to publish record: " + t.getMessage());
|
||||||
|
} finally {
|
||||||
|
IN_LOGGING.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Override public void flush() {}
|
@Override public void flush() {}
|
||||||
@Override public void close() {}
|
@Override public void close() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
redirectSystemStreams();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatLogRecord(java.util.logging.LogRecord record) {
|
||||||
|
String msg = record.getMessage();
|
||||||
|
if (msg == null) {
|
||||||
|
msg = "";
|
||||||
|
} else if (record.getResourceBundle() != null) {
|
||||||
|
try {
|
||||||
|
msg = record.getResourceBundle().getString(msg);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
Object[] params = record.getParameters();
|
||||||
|
if (params != null && params.length > 0 && msg.contains("{0}")) {
|
||||||
|
try {
|
||||||
|
msg = MessageFormat.format(msg, params);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
if (record.getThrown() != null) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
if (!msg.isEmpty()) {
|
||||||
|
pw.println(msg);
|
||||||
|
}
|
||||||
|
record.getThrown().printStackTrace(pw);
|
||||||
|
pw.flush();
|
||||||
|
msg = sw.toString();
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void redirectSystemStreams() {
|
||||||
|
try {
|
||||||
|
System.setOut(new LoggingPrintStream(ORIGINAL_OUT, Level.INFO, "System.out"));
|
||||||
|
System.setErr(new LoggingPrintStream(ORIGINAL_ERR, Level.SEVERE, "System.err"));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ORIGINAL_ERR.println("[PatchBukkit] Failed to redirect System streams: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LoggingPrintStream extends PrintStream {
|
||||||
|
private final PrintStream delegate;
|
||||||
|
private final Level level;
|
||||||
|
private final String loggerName;
|
||||||
|
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
public LoggingPrintStream(PrintStream delegate, Level level, String loggerName) {
|
||||||
|
super(delegate, true);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.level = level;
|
||||||
|
this.loggerName = loggerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
delegate.write(b);
|
||||||
|
if (b == '\n') {
|
||||||
|
flushBuffer();
|
||||||
|
} else if (b != '\r') {
|
||||||
|
buffer.write(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buf, int off, int len) {
|
||||||
|
delegate.write(buf, off, len);
|
||||||
|
for (int i = off; i < off + len; i++) {
|
||||||
|
byte b = buf[i];
|
||||||
|
if (b == '\n') {
|
||||||
|
flushBuffer();
|
||||||
|
} else if (b != '\r') {
|
||||||
|
buffer.write(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void flushBuffer() {
|
||||||
|
if (buffer.size() == 0) return;
|
||||||
|
String line = buffer.toString(StandardCharsets.UTF_8);
|
||||||
|
buffer.reset();
|
||||||
|
if (line.isEmpty() || IN_LOGGING.get()) return;
|
||||||
|
IN_LOGGING.set(true);
|
||||||
|
try {
|
||||||
|
LogLevel logLevel = LEVEL_MAP.getOrDefault(level, LogLevel.INFO);
|
||||||
|
try {
|
||||||
|
NativeBridgeFfi.sendLog(
|
||||||
|
SendLogRequest.newBuilder()
|
||||||
|
.setLevel(logLevel)
|
||||||
|
.setMessage(line)
|
||||||
|
.setLoggerName(loggerName)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
} finally {
|
||||||
|
IN_LOGGING.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,6 +351,21 @@ public class PatchBukkitServer implements Server {
|
|||||||
this.onlinePlayersByName.put(player.getName().toLowerCase(), player);
|
this.onlinePlayersByName.put(player.getName().toLowerCase(), player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void registerPlayer(String uuidStr, String name, boolean isOp) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(uuidStr);
|
||||||
|
org.patchbukkit.entity.PatchBukkitPlayer player = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, name);
|
||||||
|
if (isOp) {
|
||||||
|
player.setOp(true);
|
||||||
|
}
|
||||||
|
if (org.bukkit.Bukkit.getServer() instanceof PatchBukkitServer server) {
|
||||||
|
server.registerPlayer(player);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to register player: " + name, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PatchBukkitEventManager getEventManager() {
|
public PatchBukkitEventManager getEventManager() {
|
||||||
return this.pluginManager.getEventManager();
|
return this.pluginManager.getEventManager();
|
||||||
}
|
}
|
||||||
@ -312,10 +453,11 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getMinecraftVersion() {
|
public @NotNull String getMinecraftVersion() {
|
||||||
// TODO Auto-generated method stub
|
String version = this.bukkitVersion;
|
||||||
throw new UnsupportedOperationException(
|
if (version != null && version.contains("-")) {
|
||||||
"Unimplemented method 'getMinecraftVersion'"
|
return version.split("-")[0];
|
||||||
);
|
}
|
||||||
|
return version != null && !version.equals("Unknown-Version") ? version : "1.21.4";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -325,56 +467,36 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxPlayers() {
|
public int getMaxPlayers() {
|
||||||
// TODO Auto-generated method stub
|
return 20;
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'getMaxPlayers'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMaxPlayers(int maxPlayers) {
|
public void setMaxPlayers(int maxPlayers) {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'setMaxPlayers'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPort() {
|
public int getPort() {
|
||||||
// TODO Auto-generated method stub
|
return 25565;
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'getPort'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewDistance() {
|
public int getViewDistance() {
|
||||||
// TODO Auto-generated method stub
|
return 10;
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'getViewDistance'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSimulationDistance() {
|
public int getSimulationDistance() {
|
||||||
// TODO Auto-generated method stub
|
return 10;
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'getSimulationDistance'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getIp() {
|
public @NotNull String getIp() {
|
||||||
// TODO Auto-generated method stub
|
return "127.0.0.1";
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getIp'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getWorldType() {
|
public @NotNull String getWorldType() {
|
||||||
// TODO Auto-generated method stub
|
return "DEFAULT";
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Unimplemented method 'getWorldType'"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -79,6 +79,8 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
|||||||
registeredDirectly = true;
|
registeredDirectly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
command.register(this);
|
||||||
|
|
||||||
registerVariants(label, command);
|
registerVariants(label, command);
|
||||||
registerVariants(fallbackPrefix + ":" + label, command);
|
registerVariants(fallbackPrefix + ":" + label, command);
|
||||||
if (!clean.isEmpty()) {
|
if (!clean.isEmpty()) {
|
||||||
@ -105,7 +107,8 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
|||||||
.setPluginName(fallbackPrefix)
|
.setPluginName(fallbackPrefix)
|
||||||
.build();
|
.build();
|
||||||
NativeBridgeFfi.registerCommand(request);
|
NativeBridgeFfi.registerCommand(request);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable t) {
|
||||||
|
server.getLogger().log(java.util.logging.Level.WARNING, "Failed to register command to native bridge: " + label, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
return registeredDirectly;
|
return registeredDirectly;
|
||||||
@ -140,12 +143,66 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||||
return command.execute(sender, rawLabel, args);
|
String executedLabel = rawLabel.startsWith("//") ? rawLabel : cleanLabel(rawLabel);
|
||||||
|
return command.execute(sender, executedLabel, args);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new CommandException("Unhandled exception executing '" + cmdLine + "'", ex);
|
throw new CommandException("Unhandled exception executing '" + cmdLine + "'", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean dispatchRaw(String senderUuid, String senderName, boolean isOp, String commandLine) {
|
||||||
|
try {
|
||||||
|
org.bukkit.command.CommandSender sender;
|
||||||
|
if (senderUuid != null && !senderUuid.isBlank()) {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
||||||
|
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
||||||
|
if (sender == null) {
|
||||||
|
sender = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, senderName != null ? senderName : "Player");
|
||||||
|
}
|
||||||
|
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
||||||
|
p.setOp(isOp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender = org.bukkit.Bukkit.getConsoleSender();
|
||||||
|
}
|
||||||
|
return org.bukkit.Bukkit.dispatchCommand(sender, commandLine);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Error dispatching command: " + commandLine, t);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] tabCompleteRaw(String senderUuid, String senderName, boolean isOp, String fullCommand, String worldName, double x, double y, double z) {
|
||||||
|
try {
|
||||||
|
org.bukkit.command.CommandSender sender;
|
||||||
|
if (senderUuid != null && !senderUuid.isBlank()) {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
||||||
|
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
||||||
|
if (sender == null) {
|
||||||
|
sender = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, senderName != null ? senderName : "Player");
|
||||||
|
}
|
||||||
|
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
||||||
|
p.setOp(isOp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender = org.bukkit.Bukkit.getConsoleSender();
|
||||||
|
}
|
||||||
|
Location loc = null;
|
||||||
|
if (worldName != null && !worldName.isBlank()) {
|
||||||
|
org.bukkit.World world = org.patchbukkit.world.PatchBukkitWorld.getOrCreate(worldName);
|
||||||
|
loc = new Location(world, x, y, z);
|
||||||
|
}
|
||||||
|
List<String> list = org.bukkit.Bukkit.getServer().getCommandMap().tabComplete(sender, fullCommand, loc);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
return list.toArray(new String[0]);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Error during tab completion: " + fullCommand, t);
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearCommands() {
|
public void clearCommands() {
|
||||||
knownCommands.clear();
|
knownCommands.clear();
|
||||||
|
|||||||
@ -1240,15 +1240,18 @@ public class PatchBukkitPlayer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getAllowFlight() {
|
public boolean getAllowFlight() {
|
||||||
return NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).getAllowFlying();
|
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid));
|
||||||
|
return abilities != null && abilities.getAllowFlying();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAllowFlight(boolean flight) {
|
public void setAllowFlight(boolean flight) {
|
||||||
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).toBuilder();
|
var playerUuid = BridgeUtils.convertUuid(this.uuid);
|
||||||
abilities.setAllowFlying(flight);
|
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||||
if (abilities.getFlying()) abilities.setFlying(false);
|
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(BridgeUtils.convertUuid(this.uuid)).build());
|
builder.setAllowFlying(flight);
|
||||||
|
if (builder.getFlying()) builder.setFlying(false);
|
||||||
|
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1335,14 +1338,17 @@ public class PatchBukkitPlayer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFlying() {
|
public boolean isFlying() {
|
||||||
return NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).getFlying();
|
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid));
|
||||||
|
return abilities != null && abilities.getFlying();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFlying(boolean value) {
|
public void setFlying(boolean value) {
|
||||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setFlying(value).build();
|
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||||
|
builder.setFlying(value);
|
||||||
|
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1351,8 +1357,10 @@ public class PatchBukkitPlayer
|
|||||||
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
||||||
}
|
}
|
||||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setFlySpeed(value).build();
|
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||||
|
builder.setFlySpeed(value);
|
||||||
|
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1361,18 +1369,22 @@ public class PatchBukkitPlayer
|
|||||||
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
||||||
}
|
}
|
||||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setWalkSpeed(value).build();
|
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||||
|
builder.setWalkSpeed(value);
|
||||||
|
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getFlySpeed() {
|
public float getFlySpeed() {
|
||||||
return NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).getFlySpeed();
|
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid));
|
||||||
|
return abilities != null ? abilities.getFlySpeed() : 0.05f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWalkSpeed() {
|
public float getWalkSpeed() {
|
||||||
return NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).getWalkSpeed();
|
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid));
|
||||||
|
return abilities != null ? abilities.getWalkSpeed() : 0.1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import patchbukkit.events.BlockIgniteEvent;
|
|||||||
import patchbukkit.events.ServerCommandEvent;
|
import patchbukkit.events.ServerCommandEvent;
|
||||||
import patchbukkit.events.SignChangeEvent;
|
import patchbukkit.events.SignChangeEvent;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class PatchBukkitEventFactory {
|
public class PatchBukkitEventFactory {
|
||||||
@ -44,11 +45,22 @@ public class PatchBukkitEventFactory {
|
|||||||
Event event = Event.parseFrom(data);
|
Event event = Event.parseFrom(data);
|
||||||
return createEvent(event);
|
return createEvent(event);
|
||||||
} catch (InvalidProtocolBufferException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
LOGGER.severe("Failed to parse Event: " + e.getMessage());
|
LOGGER.log(Level.SEVERE, "Failed to parse Event", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] fireEventFromBytes(byte[] data, String pluginName) {
|
||||||
|
org.bukkit.event.Event event = createEventFromBytes(data);
|
||||||
|
if (event == null) {
|
||||||
|
return FireEventResponse.newBuilder().setCancelled(false).build().toByteArray();
|
||||||
|
}
|
||||||
|
if (Bukkit.getServer() instanceof org.patchbukkit.PatchBukkitServer server) {
|
||||||
|
server.getEventManager().fireEvent(event, pluginName);
|
||||||
|
}
|
||||||
|
return toFireEventResponse(event);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static org.bukkit.event.Event createEvent(@NotNull Event event) {
|
public static org.bukkit.event.Event createEvent(@NotNull Event event) {
|
||||||
try {
|
try {
|
||||||
@ -427,7 +439,10 @@ public class PatchBukkitEventFactory {
|
|||||||
java.util.UUID uuid = java.util.UUID.fromString(uuidStr);
|
java.util.UUID uuid = java.util.UUID.fromString(uuidStr);
|
||||||
Player player = Bukkit.getServer().getPlayer(uuid);
|
Player player = Bukkit.getServer().getPlayer(uuid);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
LOGGER.warning("EventFactory: Player not found for UUID " + uuidStr);
|
player = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, "Player");
|
||||||
|
if (Bukkit.getServer() instanceof org.patchbukkit.PatchBukkitServer server) {
|
||||||
|
server.registerPlayer(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return player;
|
return player;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
|
|||||||
@ -53,6 +53,8 @@ public class PatchBukkitEventManager {
|
|||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Set<String> registeredBridgeEvents = java.util.concurrent.ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
public void callEvent(@NotNull Event event) throws IllegalStateException {
|
public void callEvent(@NotNull Event event) throws IllegalStateException {
|
||||||
if (event.isAsynchronous() && this.server.isPrimaryThread()) {
|
if (event.isAsynchronous() && this.server.isPrimaryThread()) {
|
||||||
throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
|
throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
|
||||||
@ -60,286 +62,8 @@ public class PatchBukkitEventManager {
|
|||||||
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = CallEventRequest.newBuilder();
|
|
||||||
switch (event.getEventName()) {
|
|
||||||
case "org.bukkit.event.player.PlayerJoinEvent":
|
|
||||||
var castedEvent = (org.bukkit.event.player.PlayerJoinEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerJoin(
|
|
||||||
PlayerJoinEvent.newBuilder()
|
|
||||||
.setJoinMessage(castedEvent.joinMessage().toString())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedEvent.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerQuitEvent":
|
|
||||||
var castedQuit = (org.bukkit.event.player.PlayerQuitEvent) event;
|
|
||||||
String qMsg = castedQuit.quitMessage() != null ? castedQuit.quitMessage().toString() : "";
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerQuit(
|
|
||||||
PlayerQuitEvent.newBuilder()
|
|
||||||
.setQuitMessage(qMsg)
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedQuit.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerGameModeChangeEvent":
|
|
||||||
var castedGM = (org.bukkit.event.player.PlayerGameModeChangeEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerGamemodeChange(
|
|
||||||
PlayerGameModeChangeEvent.newBuilder()
|
|
||||||
.setNewGamemode(castedGM.getNewGameMode().name())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedGM.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerInteractEvent":
|
|
||||||
var castedInteract = (org.bukkit.event.player.PlayerInteractEvent) event;
|
|
||||||
var block = castedInteract.getClickedBlock();
|
|
||||||
int x = block != null ? block.getX() : 0;
|
|
||||||
int y = block != null ? block.getY() : 0;
|
|
||||||
int z = block != null ? block.getZ() : 0;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerInteract(
|
|
||||||
PlayerInteractEvent.newBuilder()
|
|
||||||
.setAction(castedInteract.getAction().name())
|
|
||||||
.setClickedX(x)
|
|
||||||
.setClickedY(y)
|
|
||||||
.setClickedZ(z)
|
|
||||||
.setBlockFace(castedInteract.getBlockFace().name())
|
|
||||||
.setHand(castedInteract.getHand() != null ? castedInteract.getHand().name() : "HAND")
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedInteract.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.server.PluginEnableEvent":
|
|
||||||
var castedEnable = (org.bukkit.event.server.PluginEnableEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPluginEnable(
|
|
||||||
PluginEnableEvent.newBuilder()
|
|
||||||
.setPluginName(castedEnable.getPlugin().getName()).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.SignChangeEvent":
|
|
||||||
var castedSign = (org.bukkit.event.block.SignChangeEvent) event;
|
|
||||||
var signBlock = castedSign.getBlock();
|
|
||||||
var signBuilder = patchbukkit.events.SignChangeEvent.newBuilder()
|
|
||||||
.setBlockX(signBlock.getX())
|
|
||||||
.setBlockY(signBlock.getY())
|
|
||||||
.setBlockZ(signBlock.getZ());
|
|
||||||
if (castedSign.getPlayer() != null) {
|
|
||||||
signBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedSign.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
for (String line : castedSign.getLines()) {
|
|
||||||
signBuilder.addLines(line != null ? line : "");
|
|
||||||
}
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setSignChange(signBuilder.build()).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockDamageEvent":
|
|
||||||
var castedDamage = (org.bukkit.event.block.BlockDamageEvent) event;
|
|
||||||
var dmgBlock = castedDamage.getBlock();
|
|
||||||
var dmgBuilder = patchbukkit.events.BlockDamageEvent.newBuilder()
|
|
||||||
.setBlockX(dmgBlock.getX())
|
|
||||||
.setBlockY(dmgBlock.getY())
|
|
||||||
.setBlockZ(dmgBlock.getZ())
|
|
||||||
.setInstaBreak(castedDamage.getInstaBreak());
|
|
||||||
if (castedDamage.getPlayer() != null) {
|
|
||||||
dmgBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedDamage.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockDamage(dmgBuilder.build()).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockIgniteEvent":
|
|
||||||
var castedIgnite = (org.bukkit.event.block.BlockIgniteEvent) event;
|
|
||||||
var igniteBlock = castedIgnite.getBlock();
|
|
||||||
var igniteBuilder = patchbukkit.events.BlockIgniteEvent.newBuilder()
|
|
||||||
.setBlockX(igniteBlock.getX())
|
|
||||||
.setBlockY(igniteBlock.getY())
|
|
||||||
.setBlockZ(igniteBlock.getZ())
|
|
||||||
.setIgniteCause(castedIgnite.getCause() != null ? castedIgnite.getCause().name() : "FLINT_AND_STEEL");
|
|
||||||
if (castedIgnite.getPlayer() != null) {
|
|
||||||
igniteBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedIgnite.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockIgnite(igniteBuilder.build()).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockGrowEvent":
|
|
||||||
var castedGrow = (org.bukkit.event.block.BlockGrowEvent) event;
|
|
||||||
var growBlock = castedGrow.getBlock();
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockGrow(
|
|
||||||
patchbukkit.events.BlockGrowEvent.newBuilder()
|
|
||||||
.setBlockX(growBlock.getX())
|
|
||||||
.setBlockY(growBlock.getY())
|
|
||||||
.setBlockZ(growBlock.getZ())
|
|
||||||
.setNewBlockType(castedGrow.getNewState().getType().name()).build()
|
|
||||||
).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockFormEvent":
|
|
||||||
var castedForm = (org.bukkit.event.block.BlockFormEvent) event;
|
|
||||||
var formBlock = castedForm.getBlock();
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockForm(
|
|
||||||
patchbukkit.events.BlockFormEvent.newBuilder()
|
|
||||||
.setBlockX(formBlock.getX())
|
|
||||||
.setBlockY(formBlock.getY())
|
|
||||||
.setBlockZ(formBlock.getZ())
|
|
||||||
.setNewBlockType(castedForm.getNewState().getType().name()).build()
|
|
||||||
).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockFadeEvent":
|
|
||||||
var castedFade = (org.bukkit.event.block.BlockFadeEvent) event;
|
|
||||||
var fadeBlock = castedFade.getBlock();
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockFade(
|
|
||||||
patchbukkit.events.BlockFadeEvent.newBuilder()
|
|
||||||
.setBlockX(fadeBlock.getX())
|
|
||||||
.setBlockY(fadeBlock.getY())
|
|
||||||
.setBlockZ(fadeBlock.getZ())
|
|
||||||
.setNewBlockType(castedFade.getNewState().getType().name()).build()
|
|
||||||
).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.server.ServerCommandEvent":
|
|
||||||
var castedCmd = (org.bukkit.event.server.ServerCommandEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setServerCommand(
|
|
||||||
patchbukkit.events.ServerCommandEvent.newBuilder()
|
|
||||||
.setSenderName(castedCmd.getSender().getName())
|
|
||||||
.setCommand(castedCmd.getCommand()).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.server.PluginDisableEvent":
|
|
||||||
var castedDisable = (org.bukkit.event.server.PluginDisableEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPluginDisable(
|
|
||||||
PluginDisableEvent.newBuilder()
|
|
||||||
.setPluginName(castedDisable.getPlugin().getName()).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockBreakEvent":
|
|
||||||
var castedBreak = (org.bukkit.event.block.BlockBreakEvent) event;
|
|
||||||
var breakBlock = castedBreak.getBlock();
|
|
||||||
var bBuilder = BlockBreakEvent.newBuilder()
|
|
||||||
.setBlockX(breakBlock.getX())
|
|
||||||
.setBlockY(breakBlock.getY())
|
|
||||||
.setBlockZ(breakBlock.getZ())
|
|
||||||
.setBlockType(breakBlock.getType().name())
|
|
||||||
.setExp(castedBreak.getExpToDrop())
|
|
||||||
.setDropItems(castedBreak.isDropItems());
|
|
||||||
if (castedBreak.getPlayer() != null) {
|
|
||||||
bBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedBreak.getPlayer().getUniqueId()));
|
|
||||||
}
|
|
||||||
request.setEvent(patchbukkit.events.Event.newBuilder().setBlockBreak(bBuilder.build()).build());
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.block.BlockPlaceEvent":
|
|
||||||
var castedPlace = (org.bukkit.event.block.BlockPlaceEvent) event;
|
|
||||||
var placedBlock = castedPlace.getBlockPlaced();
|
|
||||||
var againstBlock = castedPlace.getBlockAgainst();
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setBlockPlace(
|
|
||||||
BlockPlaceEvent.newBuilder()
|
|
||||||
.setBlockX(placedBlock.getX())
|
|
||||||
.setBlockY(placedBlock.getY())
|
|
||||||
.setBlockZ(placedBlock.getZ())
|
|
||||||
.setBlockPlacedType(placedBlock.getType().name())
|
|
||||||
.setBlockAgainstType(againstBlock.getType().name())
|
|
||||||
.setCanBuild(castedPlace.canBuild())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedPlace.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerToggleSneakEvent":
|
|
||||||
var castedSneak = (org.bukkit.event.player.PlayerToggleSneakEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerToggleSneak(
|
|
||||||
PlayerToggleSneakEvent.newBuilder()
|
|
||||||
.setIsSneaking(castedSneak.isSneaking())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedSneak.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerToggleSprintEvent":
|
|
||||||
var castedSprint = (org.bukkit.event.player.PlayerToggleSprintEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerToggleSprint(
|
|
||||||
PlayerToggleSprintEvent.newBuilder()
|
|
||||||
.setIsSprinting(castedSprint.isSprinting())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedSprint.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerToggleFlightEvent":
|
|
||||||
var castedFlight = (org.bukkit.event.player.PlayerToggleFlightEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerToggleFlight(
|
|
||||||
PlayerToggleFlightEvent.newBuilder()
|
|
||||||
.setIsFlying(castedFlight.isFlying())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedFlight.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerMoveEvent":
|
|
||||||
var castedMove = (org.bukkit.event.player.PlayerMoveEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerMove(
|
|
||||||
PlayerMoveEvent.newBuilder()
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedMove.getPlayer().getUniqueId()))
|
|
||||||
.setFrom(BridgeUtils.convertLocation(castedMove.getFrom()))
|
|
||||||
.setTo(BridgeUtils.convertLocation(castedMove.getTo())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.PlayerInteractEntityEvent":
|
|
||||||
case "org.bukkit.event.player.PlayerInteractAtEntityEvent":
|
|
||||||
var castedInteractEntity = (org.bukkit.event.player.PlayerInteractEntityEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerInteractEntity(
|
|
||||||
PlayerInteractEntityEvent.newBuilder()
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedInteractEntity.getPlayer().getUniqueId()))
|
|
||||||
.setTargetUuid(BridgeUtils.convertUuid(castedInteractEntity.getRightClicked().getUniqueId()))
|
|
||||||
.setAction("INTERACT")
|
|
||||||
.setIsSneaking(castedInteractEntity.getPlayer().isSneaking())
|
|
||||||
.build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.entity.EntityDamageByEntityEvent":
|
|
||||||
var castedDamageByEntity = (org.bukkit.event.entity.EntityDamageByEntityEvent) event;
|
|
||||||
if (castedDamageByEntity.getDamager() instanceof org.bukkit.entity.Player damagerPlayer) {
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerInteractEntity(
|
|
||||||
PlayerInteractEntityEvent.newBuilder()
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(damagerPlayer.getUniqueId()))
|
|
||||||
.setTargetUuid(BridgeUtils.convertUuid(castedDamageByEntity.getEntity().getUniqueId()))
|
|
||||||
.setAction("ATTACK")
|
|
||||||
.setIsSneaking(damagerPlayer.isSneaking())
|
|
||||||
.build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "org.bukkit.event.player.AsyncPlayerChatEvent":
|
|
||||||
case "org.bukkit.event.player.PlayerChatEvent":
|
|
||||||
var castedChat = (org.bukkit.event.player.AsyncPlayerChatEvent) event;
|
|
||||||
request.setEvent(
|
|
||||||
patchbukkit.events.Event.newBuilder().setPlayerChat(
|
|
||||||
PlayerChatEvent.newBuilder()
|
|
||||||
.setMessage(castedChat.getMessage())
|
|
||||||
.setFormat(castedChat.getFormat())
|
|
||||||
.setPlayerUuid(BridgeUtils.convertUuid(castedChat.getPlayer().getUniqueId())).build()
|
|
||||||
).build()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var response = NativeBridgeFfi.callEvent(request.build());
|
|
||||||
|
|
||||||
boolean handledByPumpkin;
|
|
||||||
if (response == null) handledByPumpkin = false;
|
|
||||||
else handledByPumpkin = response.getHandled();
|
|
||||||
|
|
||||||
if (!handledByPumpkin) {
|
|
||||||
// Pumpkin doesn't know this event type, dispatch Java-only
|
|
||||||
callEventJavaOnly(event);
|
callEventJavaOnly(event);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java-only event dispatch for events that don't have Pumpkin equivalents.
|
* Java-only event dispatch for events that don't have Pumpkin equivalents.
|
||||||
@ -418,9 +142,19 @@ public class PatchBukkitEventManager {
|
|||||||
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
|
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
|
||||||
this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
|
this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
|
||||||
|
|
||||||
|
String eventName = entry.getKey().getName();
|
||||||
|
String key = plugin.getName() + ":" + eventName;
|
||||||
|
if (registeredBridgeEvents.add(key)) {
|
||||||
|
int maxPriority = 0;
|
||||||
for (RegisteredListener rl : entry.getValue()) {
|
for (RegisteredListener rl : entry.getValue()) {
|
||||||
int priorityOrdinal = Math.min(rl.getPriority().ordinal(), 4);
|
maxPriority = Math.max(maxPriority, Math.min(rl.getPriority().ordinal(), 4));
|
||||||
var request = RegisterEventRequest.newBuilder().setEventType(entry.getKey().getName()).setPluginName(plugin.getName()).setPriority(priorityOrdinal).setBlocking(true).build();
|
}
|
||||||
|
var request = RegisterEventRequest.newBuilder()
|
||||||
|
.setEventType(eventName)
|
||||||
|
.setPluginName(plugin.getName())
|
||||||
|
.setPriority(maxPriority)
|
||||||
|
.setBlocking(true)
|
||||||
|
.build();
|
||||||
NativeBridgeFfi.registerEvent(request);
|
NativeBridgeFfi.registerEvent(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,10 +172,17 @@ public class PatchBukkitEventManager {
|
|||||||
executor = new TimedEventExecutor(executor, plugin, null, event);
|
executor = new TimedEventExecutor(executor, plugin, null, event);
|
||||||
this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
||||||
|
|
||||||
|
String key = plugin.getName() + ":" + event.getName();
|
||||||
|
if (registeredBridgeEvents.add(key)) {
|
||||||
int priorityOrdinal = Math.min(priority.ordinal(), 4);
|
int priorityOrdinal = Math.min(priority.ordinal(), 4);
|
||||||
var request = RegisterEventRequest.newBuilder().setEventType(event.getName()).setPluginName(plugin.getName()).setPriority(priorityOrdinal).setBlocking(true).build();
|
var request = RegisterEventRequest.newBuilder()
|
||||||
|
.setEventType(event.getName())
|
||||||
|
.setPluginName(plugin.getName())
|
||||||
|
.setPriority(priorityOrdinal)
|
||||||
|
.setBlocking(true)
|
||||||
|
.build();
|
||||||
NativeBridgeFfi.registerEvent(request);
|
NativeBridgeFfi.registerEvent(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -534,5 +275,6 @@ public class PatchBukkitEventManager {
|
|||||||
|
|
||||||
public void clearEvents() {
|
public void clearEvents() {
|
||||||
HandlerList.unregisterAll();
|
HandlerList.unregisterAll();
|
||||||
|
registeredBridgeEvents.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import java.net.URL;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.PluginCommand;
|
import org.bukkit.command.PluginCommand;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
@ -17,11 +19,13 @@ import org.patchbukkit.command.PatchBukkitCommandMap;
|
|||||||
|
|
||||||
public class PatchBukkitBootstrap {
|
public class PatchBukkitBootstrap {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("PatchBukkitBootstrap");
|
||||||
|
|
||||||
public static boolean bootstrapPlugins(String pluginsDirPath) {
|
public static boolean bootstrapPlugins(String pluginsDirPath) {
|
||||||
try {
|
try {
|
||||||
File pluginsDir = new File(pluginsDirPath);
|
File pluginsDir = new File(pluginsDirPath);
|
||||||
if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
|
if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
|
||||||
System.out.println("[PatchBukkit] Plugins directory does not exist: " + pluginsDirPath);
|
LOGGER.info("[PatchBukkit] Plugins directory does not exist: " + pluginsDirPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +33,7 @@ public class PatchBukkitBootstrap {
|
|||||||
findJarsRecursive(pluginsDir, jarFiles);
|
findJarsRecursive(pluginsDir, jarFiles);
|
||||||
|
|
||||||
if (jarFiles.isEmpty()) {
|
if (jarFiles.isEmpty()) {
|
||||||
System.out.println("[PatchBukkit] No plugin JAR files found in: " + pluginsDirPath);
|
LOGGER.info("[PatchBukkit] No plugin JAR files found in: " + pluginsDirPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,8 +46,7 @@ public class PatchBukkitBootstrap {
|
|||||||
holders.put(holder.name.toLowerCase(Locale.ENGLISH), holder);
|
holders.put(holder.name.toLowerCase(Locale.ENGLISH), holder);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Failed to parse plugin JAR " + jarFile.getName() + ": " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to parse plugin JAR " + jarFile.getName(), t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,8 +56,7 @@ public class PatchBukkitBootstrap {
|
|||||||
try {
|
try {
|
||||||
loadAndRegisterPlugin(holder, holders);
|
loadAndRegisterPlugin(holder, holders);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Failed to load plugin " + holder.name + ": " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to load plugin " + holder.name, t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +66,9 @@ public class PatchBukkitBootstrap {
|
|||||||
if (holder.pluginInstance != null) {
|
if (holder.pluginInstance != null) {
|
||||||
try {
|
try {
|
||||||
pm.enablePlugin(holder.pluginInstance);
|
pm.enablePlugin(holder.pluginInstance);
|
||||||
System.out.println("[PatchBukkit] Enabled plugin: " + holder.name);
|
LOGGER.info("[PatchBukkit] Enabled plugin: " + holder.name);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Error enabling plugin " + holder.name + ": " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error enabling plugin " + holder.name, t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,8 +76,7 @@ public class PatchBukkitBootstrap {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Fatal error during bootstrap: " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Fatal error during bootstrap", t);
|
||||||
t.printStackTrace();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,14 +210,17 @@ public class PatchBukkitBootstrap {
|
|||||||
try {
|
try {
|
||||||
plugin.onLoad();
|
plugin.onLoad();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
System.err.println("[PatchBukkit] Error during onLoad() for " + holder.name + ": " + t.getMessage());
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + holder.name, t);
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.pluginInstance = plugin;
|
holder.pluginInstance = plugin;
|
||||||
|
registerPluginCommands(plugin, holder.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerPluginCommands(Plugin plugin, PluginDescriptionFile description) {
|
||||||
|
if (plugin == null || description == null) return;
|
||||||
if (Bukkit.getCommandMap() instanceof PatchBukkitCommandMap commandMap) {
|
if (Bukkit.getCommandMap() instanceof PatchBukkitCommandMap commandMap) {
|
||||||
Map<String, Map<String, Object>> commands = holder.description.getCommands();
|
Map<String, Map<String, Object>> commands = description.getCommands();
|
||||||
if (commands != null) {
|
if (commands != null) {
|
||||||
for (Map.Entry<String, Map<String, Object>> entry : commands.entrySet()) {
|
for (Map.Entry<String, Map<String, Object>> entry : commands.entrySet()) {
|
||||||
String cmdName = entry.getKey();
|
String cmdName = entry.getKey();
|
||||||
@ -247,8 +250,8 @@ public class PatchBukkitBootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandMap.register(cmdName, holder.name, pluginCmd);
|
commandMap.register(cmdName, description.getName(), pluginCmd);
|
||||||
System.out.println("[PatchBukkit] Registered command: /" + cmdName + " for plugin " + holder.name);
|
System.out.println("[PatchBukkit] Registered command: /" + cmdName + " for plugin " + description.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,10 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
private final ThreadLocal<Set<NamespacedKey>> inFallback = ThreadLocal.withInitial(HashSet::new);
|
private final ThreadLocal<Set<NamespacedKey>> inFallback = ThreadLocal.withInitial(HashSet::new);
|
||||||
|
|
||||||
private final RegistryKey<B> registryKey;
|
private final RegistryKey<B> registryKey;
|
||||||
private boolean initialized = false;
|
private final RegistryType registryType;
|
||||||
|
private final Function<GetRegistryDataResponse, List<P>> extractor;
|
||||||
|
private final Function<P, B> factory;
|
||||||
|
private volatile boolean initialized = false;
|
||||||
|
|
||||||
public PatchBukkitRegistry(RegistryKey<B> registryKey) {
|
public PatchBukkitRegistry(RegistryKey<B> registryKey) {
|
||||||
this(null, null, null, registryKey);
|
this(null, null, null, registryKey);
|
||||||
@ -51,19 +54,49 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
RegistryKey<B> registryKey
|
RegistryKey<B> registryKey
|
||||||
) {
|
) {
|
||||||
this.registryKey = registryKey;
|
this.registryKey = registryKey;
|
||||||
if (registryType != null || extractor != null || factory != null) {
|
this.registryType = registryType;
|
||||||
initialize(registryType, extractor, factory);
|
this.extractor = extractor;
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ensureInitialized() {
|
||||||
|
if (initialized) return;
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
initializeInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized void initialize(
|
private void initializeInternal() {
|
||||||
RegistryType registryType,
|
if (registryType != null) {
|
||||||
Function<GetRegistryDataResponse, List<P>> extractor,
|
try {
|
||||||
Function<P, B> factory
|
GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder()
|
||||||
) {
|
.setRegistry(registryType)
|
||||||
if (initialized) return;
|
.build();
|
||||||
initialized = true;
|
|
||||||
|
GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request);
|
||||||
|
if (response != null && extractor != null) {
|
||||||
|
List<P> protoEntries = extractor.apply(response);
|
||||||
|
if (protoEntries != null) {
|
||||||
|
for (P protoEntry : protoEntries) {
|
||||||
|
if (protoEntry == null) continue;
|
||||||
|
try {
|
||||||
|
B value = factory != null ? factory.apply(protoEntry) : null;
|
||||||
|
if (value != null && value.getKey() != null) {
|
||||||
|
entries.put(value.getKey(), value);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Skip invalid entry safely
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Ignore FFI or RPC failure safely
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-discover pre-registered Paper/Bukkit constants for this registry
|
// Auto-discover pre-registered Paper/Bukkit constants for this registry
|
||||||
if (registryKey != null) {
|
if (registryKey != null) {
|
||||||
@ -89,6 +122,8 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
|
} else if (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey.key().value())) {
|
||||||
|
// Do not reflectively autoDiscover Sound.class because Sound.<clinit> depends on Registry.SOUNDS!
|
||||||
} else {
|
} else {
|
||||||
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
||||||
if (valueClass != null) {
|
if (valueClass != null) {
|
||||||
@ -97,34 +132,6 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registryType != null) {
|
|
||||||
try {
|
|
||||||
GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder()
|
|
||||||
.setRegistry(registryType)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request);
|
|
||||||
if (response != null && extractor != null) {
|
|
||||||
List<P> protoEntries = extractor.apply(response);
|
|
||||||
if (protoEntries != null) {
|
|
||||||
for (P protoEntry : protoEntries) {
|
|
||||||
if (protoEntry == null) continue;
|
|
||||||
try {
|
|
||||||
B value = factory.apply(protoEntry);
|
|
||||||
if (value != null && value.getKey() != null) {
|
|
||||||
entries.put(value.getKey(), value);
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// Skip invalid entry safely
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// Ignore FFI or RPC failure safely
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -162,6 +169,7 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
if (key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
ensureInitialized();
|
||||||
B value = entries.get(key);
|
B value = entries.get(key);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return value;
|
return value;
|
||||||
@ -228,11 +236,13 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasTag(TagKey<B> key) {
|
public boolean hasTag(TagKey<B> key) {
|
||||||
|
ensureInitialized();
|
||||||
return key != null && tags.containsKey(key.key().asString());
|
return key != null && tags.containsKey(key.key().asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Tag<B> getTag(TagKey<B> key) {
|
public @NonNull Tag<B> getTag(TagKey<B> key) {
|
||||||
|
ensureInitialized();
|
||||||
PatchBukkitTag<B> tag = tags.get(key.key().asString());
|
PatchBukkitTag<B> tag = tags.get(key.key().asString());
|
||||||
if (tag == null) {
|
if (tag == null) {
|
||||||
throw new NoSuchElementException("Unknown tag: " + key.key().asString());
|
throw new NoSuchElementException("Unknown tag: " + key.key().asString());
|
||||||
@ -246,25 +256,30 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Collection<Tag<B>> getTags() {
|
public @NonNull Collection<Tag<B>> getTags() {
|
||||||
|
ensureInitialized();
|
||||||
return Collections.unmodifiableCollection(tags.values());
|
return Collections.unmodifiableCollection(tags.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Stream<B> stream() {
|
public @NonNull Stream<B> stream() {
|
||||||
|
ensureInitialized();
|
||||||
return entries.values().stream();
|
return entries.values().stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Stream<NamespacedKey> keyStream() {
|
public @NonNull Stream<NamespacedKey> keyStream() {
|
||||||
|
ensureInitialized();
|
||||||
return entries.keySet().stream();
|
return entries.keySet().stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
|
ensureInitialized();
|
||||||
return entries.size();
|
return entries.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Iterator<B> iterator() {
|
public @NonNull Iterator<B> iterator() {
|
||||||
|
ensureInitialized();
|
||||||
return Collections.unmodifiableCollection(entries.values()).iterator();
|
return Collections.unmodifiableCollection(entries.values()).iterator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,24 +53,16 @@ public class PatchBukkitRegistryAccess extends io.papermc.paper.registry.PaperRe
|
|||||||
return (Registry<T>) existing;
|
return (Registry<T>) existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchBukkitRegistry reg = new PatchBukkitRegistry(registryKey);
|
PatchBukkitRegistry reg;
|
||||||
Registry<?> winner = instances.putIfAbsent(registryKey, reg);
|
|
||||||
if (winner != null) {
|
|
||||||
return (Registry<T>) winner;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(registryKey);
|
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(registryKey);
|
||||||
if (factoryEntry != null) {
|
if (factoryEntry != null) {
|
||||||
reg.initialize(factoryEntry.registryType(), factoryEntry.extractor(), factoryEntry.factory());
|
reg = new PatchBukkitRegistry(factoryEntry.registryType(), factoryEntry.extractor(), factoryEntry.factory(), registryKey);
|
||||||
} else {
|
} else {
|
||||||
reg.initialize(null, null, null);
|
reg = new PatchBukkitRegistry(registryKey);
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// Ignore failure during registry initialization safely
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Registry<T>) reg;
|
Registry<?> winner = instances.putIfAbsent(registryKey, reg);
|
||||||
|
return (Registry<T>) (winner != null ? winner : reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "deprecation"})
|
@SuppressWarnings({"unchecked", "deprecation"})
|
||||||
|
|||||||
@ -55,6 +55,15 @@ public class PatchBukkitRegistryAccessTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSoundClassInitialization() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
org.bukkit.Sound sound = org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP;
|
||||||
|
assertNotNull(sound, "Sound constant must initialize successfully without ExceptionInInitializerError");
|
||||||
|
assertNotNull(sound.getKey(), "Sound key must not be null");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInspectBlockTypeAndDataMethods() {
|
public void testInspectBlockTypeAndDataMethods() {
|
||||||
System.out.println("=== BLOCK TYPE METHODS ===");
|
System.out.println("=== BLOCK TYPE METHODS ===");
|
||||||
|
|||||||
1176
rust/Cargo.lock
generated
1176
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch
|
|||||||
tokio = "1"
|
tokio = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
glob = "0.3.4"
|
glob = "0.3.4"
|
||||||
j4rs = { version = "0.25.2" }
|
jni = { version = "0.22.4", features = ["invocation"] }
|
||||||
rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] }
|
rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
zip = "8.6"
|
zip = "8.6"
|
||||||
@ -39,7 +39,6 @@ prost = "0.14"
|
|||||||
toml = "1.1"
|
toml = "1.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
j4rs = { version = "0.25.2", artifact = "cdylib", lib = true }
|
|
||||||
env_logger = "0.11.11"
|
env_logger = "0.11.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
prost-build = "0.14"
|
prost-build = "0.14"
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use j4rs::{JvmBuilder, MavenArtifactRepo, MavenSettings};
|
|
||||||
|
|
||||||
pub fn setup_java(base: PathBuf) {
|
pub fn setup_java(base: PathBuf) {
|
||||||
let resources = base.join("resources");
|
let resources = base.join("resources");
|
||||||
let deps = resources.join("deps");
|
let jassets = resources.join("jassets");
|
||||||
|
|
||||||
let mut java_path = base.clone();
|
let mut java_path = base;
|
||||||
java_path.pop();
|
java_path.pop();
|
||||||
let java_path = java_path.join("java");
|
let java_path = java_path.join("java");
|
||||||
|
|
||||||
@ -16,72 +14,15 @@ pub fn setup_java(base: PathBuf) {
|
|||||||
.join("libs")
|
.join("libs")
|
||||||
.join("patchbukkit.jar");
|
.join("patchbukkit.jar");
|
||||||
|
|
||||||
let jassets = resources.join("jassets");
|
if !patchbukkit_jar.exists() {
|
||||||
let mut classpath_entries = Vec::new();
|
|
||||||
|
|
||||||
if let Ok(entries) = fs::read_dir(&jassets) {
|
|
||||||
for entry in entries.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str())
|
|
||||||
&& filename.starts_with("j4rs")
|
|
||||||
&& filename.ends_with(".jar")
|
|
||||||
{
|
|
||||||
classpath_entries.push(j4rs::ClasspathEntry::new(&path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut jvm_builder = JvmBuilder::new();
|
|
||||||
let mut jvm_builder = jvm_builder
|
|
||||||
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
|
||||||
"papermc::https://repo.papermc.io/repository/maven-public/",
|
|
||||||
)]))
|
|
||||||
.skip_setting_native_lib()
|
|
||||||
.with_base_path(&resources)
|
|
||||||
.java_opts(vec![
|
|
||||||
j4rs::JavaOpt::new("--enable-native-access=ALL-UNNAMED"),
|
|
||||||
j4rs::JavaOpt::new("--enable-final-field-mutation=ALL-UNNAMED"),
|
|
||||||
j4rs::JavaOpt::new("--add-opens=java.base/java.lang=ALL-UNNAMED"),
|
|
||||||
j4rs::JavaOpt::new("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"),
|
|
||||||
j4rs::JavaOpt::new("-Dcom.google.protobuf.useUnsafe=false"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for entry in classpath_entries {
|
|
||||||
jvm_builder = jvm_builder.classpath_entry(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _jvm = jvm_builder
|
|
||||||
.build()
|
|
||||||
.map_err(|err| format!("jvm failed to init: {err:?}"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if !&patchbukkit_jar.exists() {
|
|
||||||
panic!(
|
panic!(
|
||||||
"Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!"
|
"Failed to find patchbukkit.jar, build the java library first by running `./gradlew build` in the java directory!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&jassets).unwrap();
|
fs::create_dir_all(&jassets).unwrap();
|
||||||
|
let dest_patchbukkit = jassets.join("patchbukkit.jar");
|
||||||
let cdylib = std::env::var("CARGO_CDYLIB_FILE_J4RS").unwrap();
|
fs::copy(&patchbukkit_jar, &dest_patchbukkit)
|
||||||
let cdylib = PathBuf::from(cdylib);
|
.map_err(|err| format!("Failed to copy patchbukkit.jar to resources/jassets: {err:?}"))
|
||||||
|
|
||||||
let mut cdylib_to = deps;
|
|
||||||
fs::create_dir_all(&cdylib_to).unwrap();
|
|
||||||
|
|
||||||
let original_name = cdylib.file_name().unwrap().to_string_lossy();
|
|
||||||
let mut target_name = original_name.split('-').next().unwrap().to_string();
|
|
||||||
if !target_name.ends_with(".dll")
|
|
||||||
&& !target_name.ends_with(".so")
|
|
||||||
&& !target_name.ends_with(".dylib")
|
|
||||||
&& let Some(ext) = cdylib.extension()
|
|
||||||
{
|
|
||||||
target_name = format!("{target_name}.{}", ext.to_string_lossy());
|
|
||||||
}
|
|
||||||
|
|
||||||
cdylib_to.push(target_name);
|
|
||||||
|
|
||||||
fs::copy(&cdylib, &cdylib_to)
|
|
||||||
.map_err(|err| format!("Failed to copy j4rs native lib: {err:?}"))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,6 @@ pub unsafe extern "C" fn {fn_name}(
|
|||||||
output_len: *mut usize,
|
output_len: *mut usize,
|
||||||
) -> *mut u8 {{
|
) -> *mut u8 {{
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
tracing::debug!("{fn_name}: called with input_ptr={{:?}}, input_len={{}}", input_ptr, input_len);
|
|
||||||
if input_ptr.is_null() {{
|
if input_ptr.is_null() {{
|
||||||
tracing::warn!("{fn_name}: null input (ptr={{:?}}, len={{}})", input_ptr, input_len);
|
tracing::warn!("{fn_name}: null input (ptr={{:?}}, len={{}})", input_ptr, input_len);
|
||||||
unsafe {{ *output_len = 0 }};
|
unsafe {{ *output_len = 0 }};
|
||||||
@ -123,16 +122,13 @@ pub unsafe extern "C" fn {fn_name}(
|
|||||||
return std::ptr::null_mut();
|
return std::ptr::null_mut();
|
||||||
}};
|
}};
|
||||||
let Some(response) = {0}::{fn_name}_impl(request) else {{
|
let Some(response) = {0}::{fn_name}_impl(request) else {{
|
||||||
tracing::debug!("{fn_name}: impl returned None");
|
|
||||||
unsafe {{ *output_len = 0 }};
|
unsafe {{ *output_len = 0 }};
|
||||||
return std::ptr::null_mut();
|
return std::ptr::null_mut();
|
||||||
}};
|
}};
|
||||||
let encoded = response.encode_to_vec().into_boxed_slice();
|
let encoded = response.encode_to_vec().into_boxed_slice();
|
||||||
let len = encoded.len();
|
let len = encoded.len();
|
||||||
unsafe {{ *output_len = len }};
|
unsafe {{ *output_len = len }};
|
||||||
let ptr = Box::into_raw(encoded) as *mut u8;
|
Box::into_raw(encoded) as *mut u8
|
||||||
tracing::debug!("{fn_name}: returning ptr={{:?}}, len={{}}", ptr, len);
|
|
||||||
ptr
|
|
||||||
}}
|
}}
|
||||||
"#,
|
"#,
|
||||||
self.impl_module
|
self.impl_module
|
||||||
@ -214,47 +210,61 @@ pub unsafe extern "C" fn ffi_free_bytes(ptr: *mut u8, len: usize) {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
pub fn initialize_ffi_callbacks(jvm: &j4rs::Jvm) -> anyhow::Result<()> {{
|
pub fn initialize_ffi_callbacks(env: &mut jni::Env) -> anyhow::Result<()> {{"#
|
||||||
use j4rs::InvocationArg;"#
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for service in &state.services {
|
for service in &state.services {
|
||||||
|
let class_slash = service.java_class.replace('.', "/");
|
||||||
|
let sig = format!("({})V", "J".repeat(service.methods.len()));
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
file,
|
file,
|
||||||
r#"
|
r#"
|
||||||
// Initialize {}
|
// Initialize {class_slash}
|
||||||
if let Err(e) = jvm.invoke_static(
|
{{
|
||||||
"{}",
|
let args = ["#,
|
||||||
"init",
|
|
||||||
&["#,
|
|
||||||
service.java_class, service.java_class
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for method in &service.methods {
|
for method in &service.methods {
|
||||||
writeln!(file,
|
writeln!(
|
||||||
" InvocationArg::try_from(crate::proto::{}::{} as *const () as i64)?.into_primitive()?,",
|
file,
|
||||||
|
" jni::objects::JValue::Long(crate::proto::{}::{} as *const () as i64),",
|
||||||
service.module_path, method.fn_name
|
service.module_path, method.fn_name
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
file,
|
file,
|
||||||
r#" ],
|
r#" ];
|
||||||
|
if let Err(e) = env.call_static_method(
|
||||||
|
jni::jni_str!("{class_slash}"),
|
||||||
|
jni::jni_str!("init"),
|
||||||
|
jni::jni_sig!("{sig}"),
|
||||||
|
&args,
|
||||||
) {{
|
) {{
|
||||||
tracing::error!("Failed to initialize FFI service: {{:?}}", e);
|
env.exception_describe();
|
||||||
|
env.exception_clear();
|
||||||
|
tracing::error!("Failed to initialize FFI service {class_slash}: {{:?}}", e);
|
||||||
|
return Err(anyhow::anyhow!("Failed to initialize FFI service {class_slash}: {{e:?}}"));
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if let Err(e) = jvm.invoke_static(
|
if let Err(e) = env.call_static_method(
|
||||||
"{}",
|
jni::jni_str!("{class_slash}"),
|
||||||
"initFree",
|
jni::jni_str!("initFree"),
|
||||||
&[InvocationArg::try_from(ffi_free_bytes as *const () as i64)?.into_primitive()?],
|
jni::jni_sig!("(J)V"),
|
||||||
|
&[jni::objects::JValue::Long(ffi_free_bytes as *const () as i64)],
|
||||||
) {{
|
) {{
|
||||||
tracing::error!("Failed to initialize FFI service initFree: {{:?}}", e);
|
env.exception_describe();
|
||||||
|
env.exception_clear();
|
||||||
|
tracing::error!("Failed to initialize FFI service initFree on {class_slash}: {{:?}}", e);
|
||||||
|
return Err(anyhow::anyhow!("Failed to initialize FFI service initFree on {class_slash}: {{e:?}}"));
|
||||||
|
}}
|
||||||
}}"#,
|
}}"#,
|
||||||
service.java_class
|
|
||||||
)
|
)
|
||||||
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@ -25,8 +25,8 @@ pub struct JavaCommandExecutor {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SimpleCommandSender {
|
pub enum SimpleCommandSender {
|
||||||
Console,
|
Console,
|
||||||
/// UUID, Name
|
/// UUID, Name, is_op
|
||||||
Player(String, String),
|
Player(String, String, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnyCommandNode {
|
pub struct AnyCommandNode {
|
||||||
@ -94,7 +94,8 @@ impl ArgumentConsumer for AnyCommandNode {
|
|||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.command_tx
|
if let Err(e) = self
|
||||||
|
.command_tx
|
||||||
.send(JvmCommand::GetCommandTabComplete {
|
.send(JvmCommand::GetCommandTabComplete {
|
||||||
command_sender,
|
command_sender,
|
||||||
full_command: input.to_string(),
|
full_command: input.to_string(),
|
||||||
@ -102,9 +103,15 @@ impl ArgumentConsumer for AnyCommandNode {
|
|||||||
location,
|
location,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
{
|
||||||
|
tracing::warn!("Failed to send tab complete to JVM worker: {e}");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
rx.await.unwrap()
|
match rx.await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => Ok(None),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,10 +120,15 @@ impl From<&CommandSender> for SimpleCommandSender {
|
|||||||
fn from(val: &CommandSender) -> Self {
|
fn from(val: &CommandSender) -> Self {
|
||||||
match val {
|
match val {
|
||||||
CommandSender::Console | CommandSender::Rcon(_) | CommandSender::Dummy => Self::Console,
|
CommandSender::Console | CommandSender::Rcon(_) | CommandSender::Dummy => Self::Console,
|
||||||
CommandSender::Player(player) => Self::Player(
|
CommandSender::Player(player) => {
|
||||||
|
let is_op =
|
||||||
|
player.permission_lvl.load() >= pumpkin_util::permission::PermissionLvl::Two;
|
||||||
|
Self::Player(
|
||||||
player.gameprofile.id.to_string(),
|
player.gameprofile.id.to_string(),
|
||||||
player.gameprofile.name.clone(),
|
player.gameprofile.name.clone(),
|
||||||
),
|
is_op,
|
||||||
|
)
|
||||||
|
}
|
||||||
CommandSender::CommandBlock(_block_entity, _world) => Self::Console,
|
CommandSender::CommandBlock(_block_entity, _world) => Self::Console,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,6 +141,10 @@ impl CommandExecutor for JavaCommandExecutor {
|
|||||||
_server: &'a pumpkin::server::Server,
|
_server: &'a pumpkin::server::Server,
|
||||||
args: &'a pumpkin::command::args::ConsumedArgs<'a>,
|
args: &'a pumpkin::command::args::ConsumedArgs<'a>,
|
||||||
) -> pumpkin::command::CommandResult<'a> {
|
) -> pumpkin::command::CommandResult<'a> {
|
||||||
|
if let CommandSender::Player(player) = sender {
|
||||||
|
crate::java::native_callbacks::utils::cache_player(player.clone());
|
||||||
|
}
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let full_command = match args.get(ARG_ANY) {
|
let full_command = match args.get(ARG_ANY) {
|
||||||
Some(Arg::Msg(msg)) => {
|
Some(Arg::Msg(msg)) => {
|
||||||
@ -147,15 +163,21 @@ impl CommandExecutor for JavaCommandExecutor {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, _rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.command_tx
|
if let Err(e) = self
|
||||||
|
.command_tx
|
||||||
.send(JvmCommand::TriggerCommand {
|
.send(JvmCommand::TriggerCommand {
|
||||||
full_command,
|
full_command,
|
||||||
respond_to: tx,
|
respond_to: tx,
|
||||||
command_sender: sender.into(),
|
command_sender: sender.into(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
{
|
||||||
|
tracing::error!("Failed to send command to JVM worker: {e}");
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = rx.await;
|
||||||
Ok(1)
|
Ok(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -186,3 +208,23 @@ pub fn init_java_command(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pumpkin::command::dispatcher::CommandDispatcher;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_java_command_registration() {
|
||||||
|
let mut dispatcher = CommandDispatcher::default();
|
||||||
|
|
||||||
|
let (tx, _rx) = mpsc::channel(100);
|
||||||
|
let names = vec!["fly".to_string(), "/fly".to_string(), "//fly".to_string()];
|
||||||
|
let tree = init_java_command("fly", tx, names, "Fly command");
|
||||||
|
dispatcher.register(tree, "patchbukkit:command.fly");
|
||||||
|
|
||||||
|
assert!(dispatcher.get_tree("fly").is_ok());
|
||||||
|
assert!(dispatcher.get_tree("/fly").is_ok());
|
||||||
|
assert!(dispatcher.get_tree("//fly").is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ pub struct PatchBukkitDirectories {
|
|||||||
pub base: PathBuf,
|
pub base: PathBuf,
|
||||||
pub plugins: PathBuf,
|
pub plugins: PathBuf,
|
||||||
pub plugin_updates: PathBuf,
|
pub plugin_updates: PathBuf,
|
||||||
pub j4rs: PathBuf,
|
|
||||||
pub jassets: PathBuf,
|
pub jassets: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,8 +19,7 @@ pub fn setup_directories(server: &Context) -> Result<PatchBukkitDirectories, Str
|
|||||||
|
|
||||||
let plugins = base.join("patchbukkit-plugins");
|
let plugins = base.join("patchbukkit-plugins");
|
||||||
let plugin_updates = plugins.join("update");
|
let plugin_updates = plugins.join("update");
|
||||||
let j4rs = base.join("j4rs");
|
let jassets = base.join("jassets");
|
||||||
let jassets = j4rs.join("jassets");
|
|
||||||
|
|
||||||
fs::create_dir_all(&jassets)
|
fs::create_dir_all(&jassets)
|
||||||
.map_err(|err| format!("Failed to create jassets folder: {err:?}"))?;
|
.map_err(|err| format!("Failed to create jassets folder: {err:?}"))?;
|
||||||
@ -58,7 +56,6 @@ pub fn setup_directories(server: &Context) -> Result<PatchBukkitDirectories, Str
|
|||||||
base,
|
base,
|
||||||
plugins,
|
plugins,
|
||||||
plugin_updates,
|
plugin_updates,
|
||||||
j4rs,
|
|
||||||
jassets,
|
jassets,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin::entity::player::Player;
|
use pumpkin::entity::player::Player;
|
||||||
use pumpkin::plugin::{BoxFuture, Cancellable, EventHandler, Payload};
|
use pumpkin::plugin::{BoxFuture, Cancellable, EventHandler, Payload};
|
||||||
use pumpkin::server::Server;
|
use pumpkin::server::Server;
|
||||||
use pumpkin_api_macros::with_runtime;
|
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use crate::java::jvm::commands::JvmCommand;
|
use crate::java::jvm::commands::JvmCommand;
|
||||||
@ -52,10 +50,11 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_modifications(&mut self, server: &Arc<Server>, data: Data) -> Option<()> {
|
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||||
if let Data::PlayerJoin(event) = data {
|
if let Data::PlayerJoin(event) = data
|
||||||
self.join_message = serde_json::from_str(&event.join_message).ok()?;
|
&& let Ok(msg) = serde_json::from_str(&event.join_message)
|
||||||
server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?;
|
{
|
||||||
|
self.join_message = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@ -80,10 +79,11 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_leave::PlayerLeaveEven
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_modifications(&mut self, server: &Arc<Server>, data: Data) -> Option<()> {
|
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||||
if let Data::PlayerQuit(event) = data {
|
if let Data::PlayerQuit(event) = data
|
||||||
self.leave_message = serde_json::from_str(&event.quit_message).ok()?;
|
&& let Ok(msg) = serde_json::from_str(&event.quit_message)
|
||||||
server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?;
|
{
|
||||||
|
self.leave_message = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@ -603,23 +603,51 @@ impl<E: PatchBukkitEvent> PatchBukkitEventHandler<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[with_runtime(global)]
|
|
||||||
impl<E> EventHandler<E> for PatchBukkitEventHandler<E>
|
impl<E> EventHandler<E> for PatchBukkitEventHandler<E>
|
||||||
where
|
where
|
||||||
E: PatchBukkitEvent + Payload + Cancellable + 'static,
|
E: PatchBukkitEvent + Payload + Cancellable + 'static,
|
||||||
{
|
{
|
||||||
|
fn handle<'a>(&'a self, server: &'a Arc<Server>, event: &'a E) -> BoxFuture<'a, ()> {
|
||||||
|
let command_tx = self.command_tx.clone();
|
||||||
|
let payload = event.to_payload(server.clone());
|
||||||
|
if let Some(player) = &payload.context.player {
|
||||||
|
crate::java::native_callbacks::utils::cache_player(player.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
if let Err(e) = command_tx
|
||||||
|
.send(JvmCommand::FireEvent {
|
||||||
|
payload,
|
||||||
|
respond_to: tx,
|
||||||
|
plugin: self.plugin_name.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to send event to JVM worker: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = rx.await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_blocking<'a>(
|
fn handle_blocking<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
server: &'a Arc<Server>,
|
server: &'a Arc<Server>,
|
||||||
event: &'a mut E,
|
event: &'a mut E,
|
||||||
) -> BoxFuture<'a, ()> {
|
) -> BoxFuture<'a, ()> {
|
||||||
let command_tx = self.command_tx.clone();
|
let command_tx = self.command_tx.clone();
|
||||||
|
let payload = event.to_payload(server.clone());
|
||||||
|
if let Some(player) = &payload.context.player {
|
||||||
|
crate::java::native_callbacks::utils::cache_player(player.clone());
|
||||||
|
}
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
if let Err(e) = command_tx
|
if let Err(e) = command_tx
|
||||||
.send(JvmCommand::FireEvent {
|
.send(JvmCommand::FireEvent {
|
||||||
payload: event.to_payload(server.clone()),
|
payload,
|
||||||
respond_to: tx,
|
respond_to: tx,
|
||||||
plugin: self.plugin_name.clone(),
|
plugin: self.plugin_name.clone(),
|
||||||
})
|
})
|
||||||
@ -632,7 +660,9 @@ where
|
|||||||
match rx.await {
|
match rx.await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
event.set_cancelled(response.cancelled);
|
event.set_cancelled(response.cancelled);
|
||||||
event.apply_modifications(server, response.data.unwrap().data.unwrap());
|
if let Some(event_data) = response.data.and_then(|d| d.data) {
|
||||||
|
let _ = event.apply_modifications(server, event_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
tracing::warn!("JVM worker dropped response channel for event");
|
tracing::warn!("JVM worker dropped response channel for event");
|
||||||
|
|||||||
@ -22,9 +22,10 @@ pub enum LoadPluginResult {
|
|||||||
|
|
||||||
pub enum JvmCommand {
|
pub enum JvmCommand {
|
||||||
Initialize {
|
Initialize {
|
||||||
j4rs_path: PathBuf,
|
jassets_path: PathBuf,
|
||||||
respond_to: oneshot::Sender<Result<()>>,
|
respond_to: oneshot::Sender<Result<()>>,
|
||||||
context: Arc<Context>,
|
context: Arc<Context>,
|
||||||
|
runtime_handle: tokio::runtime::Handle,
|
||||||
command_tx: mpsc::Sender<Self>,
|
command_tx: mpsc::Sender<Self>,
|
||||||
config: PatchBukkitConfig,
|
config: PatchBukkitConfig,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use j4rs::{InvocationArg, Jvm, JvmBuilder};
|
use jni::{Env, InitArgsBuilder, JNIVersion, JavaVM};
|
||||||
use pumpkin::plugin::Context;
|
use pumpkin::plugin::Context;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ pub struct JvmWorker {
|
|||||||
pub plugin_manager: PluginManager,
|
pub plugin_manager: PluginManager,
|
||||||
pub event_manager: EventManager,
|
pub event_manager: EventManager,
|
||||||
pub command_manager: CommandManager,
|
pub command_manager: CommandManager,
|
||||||
jvm: Option<j4rs::Jvm>,
|
jvm: Option<Arc<JavaVM>>,
|
||||||
context: Option<Arc<Context>>,
|
context: Option<Arc<Context>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,21 +44,22 @@ impl JvmWorker {
|
|||||||
while let Some(command) = self.command_rx.recv().await {
|
while let Some(command) = self.command_rx.recv().await {
|
||||||
match command {
|
match command {
|
||||||
JvmCommand::Initialize {
|
JvmCommand::Initialize {
|
||||||
j4rs_path,
|
jassets_path,
|
||||||
respond_to,
|
respond_to,
|
||||||
context,
|
context,
|
||||||
|
runtime_handle,
|
||||||
command_tx,
|
command_tx,
|
||||||
config,
|
config,
|
||||||
} => {
|
} => {
|
||||||
init_callback_context(
|
init_callback_context(
|
||||||
context.clone(),
|
context.clone(),
|
||||||
tokio::runtime::Handle::current(),
|
runtime_handle,
|
||||||
command_tx.clone(),
|
command_tx.clone(),
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.context = Some(context);
|
self.context = Some(context);
|
||||||
let result = self.initialize_jvm(&j4rs_path);
|
let result = self.initialize_jvm(&jassets_path);
|
||||||
let _ = respond_to.send(result);
|
let _ = respond_to.send(result);
|
||||||
}
|
}
|
||||||
JvmCommand::LoadPlugin {
|
JvmCommand::LoadPlugin {
|
||||||
@ -105,38 +106,49 @@ impl JvmWorker {
|
|||||||
server,
|
server,
|
||||||
command_tx,
|
command_tx,
|
||||||
} => {
|
} => {
|
||||||
let jvm = match self.jvm {
|
let res = if let Some(ref jvm) = self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
let plugins_dir = plugins_dir.clone();
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
let server = server.clone();
|
||||||
};
|
let command_tx = command_tx.clone();
|
||||||
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
let _ = respond_to.send(
|
self.plugin_manager.instantiate_all_plugins(
|
||||||
self.plugin_manager
|
env,
|
||||||
.instantiate_all_plugins(
|
|
||||||
jvm,
|
|
||||||
&plugins_dir,
|
&plugins_dir,
|
||||||
&server,
|
&server,
|
||||||
command_tx,
|
command_tx,
|
||||||
&mut self.command_manager,
|
&mut self.command_manager,
|
||||||
)
|
)
|
||||||
.await,
|
})
|
||||||
);
|
.map_err(|e| anyhow::anyhow!("Failed to instantiate plugins: {e}"))
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("JVM is not initialized"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = respond_to.send(res);
|
||||||
}
|
}
|
||||||
JvmCommand::EnableAllPlugins { respond_to } => {
|
JvmCommand::EnableAllPlugins { respond_to } => {
|
||||||
let jvm = match self.jvm {
|
let res = if let Some(ref jvm) = self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
self.plugin_manager.enable_all_plugins(env)
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to enable plugins: {e}"))
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("JVM is not initialized"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = respond_to.send(self.plugin_manager.enable_all_plugins(jvm));
|
let _ = respond_to.send(res);
|
||||||
}
|
}
|
||||||
JvmCommand::DisableAllPlugins { respond_to } => {
|
JvmCommand::DisableAllPlugins { respond_to } => {
|
||||||
let jvm = match self.jvm {
|
let res = if let Some(ref jvm) = self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
self.plugin_manager.disable_all_plugins(env)
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to disable plugins: {e}"))
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("JVM is not initialized"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = respond_to.send(self.plugin_manager.disable_all_plugins(jvm));
|
let _ = respond_to.send(res);
|
||||||
}
|
}
|
||||||
JvmCommand::Shutdown { respond_to } => {
|
JvmCommand::Shutdown { respond_to } => {
|
||||||
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
||||||
@ -147,14 +159,14 @@ impl JvmWorker {
|
|||||||
plugin,
|
plugin,
|
||||||
payload,
|
payload,
|
||||||
} => {
|
} => {
|
||||||
let jvm = match self.jvm {
|
|
||||||
Some(ref jvm) => jvm,
|
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let original_event = payload.event.clone();
|
let original_event = payload.event.clone();
|
||||||
let cancelled = match self.event_manager.fire_event(jvm, payload, plugin) {
|
let response = if let Some(ref jvm) = self.jvm {
|
||||||
Ok(c) => c,
|
match jvm.attach_current_thread(
|
||||||
|
|env| -> anyhow::Result<FireEventResponse> {
|
||||||
|
self.event_manager.fire_event(env, payload, plugin)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(resp) => resp,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Failed to fire event: {e}");
|
tracing::error!("Failed to fire event: {e}");
|
||||||
FireEventResponse {
|
FireEventResponse {
|
||||||
@ -162,25 +174,32 @@ impl JvmWorker {
|
|||||||
data: Some(original_event),
|
data: Some(original_event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FireEventResponse {
|
||||||
|
cancelled: false,
|
||||||
|
data: Some(original_event),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = respond_to.send(cancelled);
|
let _ = respond_to.send(response);
|
||||||
}
|
}
|
||||||
JvmCommand::TriggerCommand {
|
JvmCommand::TriggerCommand {
|
||||||
full_command,
|
full_command,
|
||||||
command_sender,
|
command_sender,
|
||||||
respond_to,
|
respond_to,
|
||||||
} => {
|
} => {
|
||||||
let jvm = match self.jvm {
|
let res = if let Some(ref jvm) = self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
self.command_manager
|
||||||
|
.trigger_command(env, full_command, command_sender)
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to trigger command: {e}"))
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("JVM is not initialized"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let result =
|
let _ = respond_to.send(res);
|
||||||
self.command_manager
|
|
||||||
.trigger_command(jvm, full_command, command_sender);
|
|
||||||
|
|
||||||
let _ = respond_to.send(result);
|
|
||||||
}
|
}
|
||||||
JvmCommand::GetCommandTabComplete {
|
JvmCommand::GetCommandTabComplete {
|
||||||
command_sender,
|
command_sender,
|
||||||
@ -188,19 +207,26 @@ impl JvmWorker {
|
|||||||
respond_to,
|
respond_to,
|
||||||
location,
|
location,
|
||||||
} => {
|
} => {
|
||||||
let jvm = match self.jvm {
|
let res = if let Some(ref jvm) = self.jvm {
|
||||||
Some(ref jvm) => jvm,
|
match jvm.attach_current_thread(|env| -> anyhow::Result<_> {
|
||||||
None => &Jvm::attach_thread().unwrap(),
|
Ok(self.command_manager.get_tab_complete(
|
||||||
};
|
env,
|
||||||
|
|
||||||
let result = self.command_manager.get_tab_complete(
|
|
||||||
jvm,
|
|
||||||
command_sender,
|
command_sender,
|
||||||
full_command,
|
full_command,
|
||||||
location,
|
location,
|
||||||
);
|
))
|
||||||
|
}) {
|
||||||
|
Ok(inner_res) => inner_res,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to get tab complete: {e}");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
};
|
||||||
|
|
||||||
let _ = respond_to.send(result);
|
let _ = respond_to.send(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,41 +234,85 @@ impl JvmWorker {
|
|||||||
tracing::info!("JVM worker thread exited");
|
tracing::info!("JVM worker thread exited");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_jvm(&mut self, j4rs_path: &PathBuf) -> anyhow::Result<()> {
|
fn initialize_jvm(&mut self, jassets_path: &PathBuf) -> anyhow::Result<()> {
|
||||||
tracing::info!("Initializing JVM with path: {j4rs_path:?}");
|
tracing::info!("Initializing JVM with assets path: {jassets_path:?}");
|
||||||
|
|
||||||
let jassets = j4rs_path.join("jassets");
|
let mut jar_paths = Vec::new();
|
||||||
let mut classpath_entries = Vec::new();
|
|
||||||
|
|
||||||
if let Ok(entries) = std::fs::read_dir(&jassets) {
|
for entry in walkdir::WalkDir::new(jassets_path)
|
||||||
for entry in entries.flatten() {
|
.into_iter()
|
||||||
let path = entry.path();
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().is_file())
|
||||||
|
{
|
||||||
|
let path = entry.path().to_path_buf();
|
||||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str())
|
if let Some(filename) = path.file_name().and_then(|n| n.to_str())
|
||||||
&& filename.ends_with(".jar")
|
&& filename.ends_with(".jar")
|
||||||
{
|
{
|
||||||
classpath_entries.push(j4rs::ClasspathEntry::new(&path));
|
jar_paths.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if jar_paths.is_empty() {
|
||||||
|
tracing::warn!(
|
||||||
|
"No JAR files found in jassets directory ({:?})",
|
||||||
|
jassets_path
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::info!(
|
||||||
|
"Found {} JAR entries in jassets: {:?}",
|
||||||
|
jar_paths.len(),
|
||||||
|
jar_paths
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut jvm_builder = JvmBuilder::new();
|
let has_patchbukkit = jar_paths.iter().any(|p| {
|
||||||
let mut jvm_builder = jvm_builder.with_base_path(j4rs_path).java_opts(vec![
|
p.file_name()
|
||||||
j4rs::JavaOpt::new("--enable-native-access=ALL-UNNAMED"),
|
.and_then(|n| n.to_str())
|
||||||
j4rs::JavaOpt::new("--enable-final-field-mutation=ALL-UNNAMED"),
|
.is_some_and(|n| n.contains("patchbukkit"))
|
||||||
j4rs::JavaOpt::new("--add-opens=java.base/java.lang=ALL-UNNAMED"),
|
});
|
||||||
j4rs::JavaOpt::new("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"),
|
if !has_patchbukkit {
|
||||||
j4rs::JavaOpt::new("-Dcom.google.protobuf.useUnsafe=false"),
|
tracing::warn!(
|
||||||
]);
|
"patchbukkit.jar was not found in {:?}. Server classes may fail to load!",
|
||||||
|
jassets_path
|
||||||
for entry in classpath_entries {
|
);
|
||||||
jvm_builder = jvm_builder.classpath_entry(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let jvm = jvm_builder.build()?;
|
let separator = if cfg!(windows) { ";" } else { ":" };
|
||||||
|
let classpath = jar_paths
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(separator);
|
||||||
|
|
||||||
initialize_callbacks(&jvm)?;
|
let jvm_args = InitArgsBuilder::new()
|
||||||
|
.version(JNIVersion::V21)
|
||||||
|
.option(format!("-Djava.class.path={classpath}"))
|
||||||
|
.option("--enable-native-access=ALL-UNNAMED")
|
||||||
|
.option("--enable-final-field-mutation=ALL-UNNAMED")
|
||||||
|
.option("--add-opens=java.base/java.lang=ALL-UNNAMED")
|
||||||
|
.option("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED")
|
||||||
|
.option("-Dcom.google.protobuf.useUnsafe=false")
|
||||||
|
.build()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to build JVM init args: {e:?}"))?;
|
||||||
|
|
||||||
setup_patchbukkit_server(&jvm)?;
|
let jvm =
|
||||||
|
JavaVM::new(jvm_args).map_err(|e| anyhow::anyhow!("Failed to create JavaVM: {e:?}"))?;
|
||||||
|
let jvm = Arc::new(jvm);
|
||||||
|
|
||||||
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
|
initialize_callbacks(env).map_err(|e| {
|
||||||
|
tracing::error!("Failed to initialize callbacks: {e:?}");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
setup_patchbukkit_server(env).map_err(|e| {
|
||||||
|
tracing::error!("Failed to setup PatchBukkit server: {e:?}");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to attach thread for JVM initialization: {e:?}"))?;
|
||||||
|
|
||||||
self.jvm = Some(jvm);
|
self.jvm = Some(jvm);
|
||||||
|
|
||||||
@ -251,12 +321,17 @@ impl JvmWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_patchbukkit_server(jvm: &Jvm) -> anyhow::Result<()> {
|
pub fn setup_patchbukkit_server(env: &mut Env) -> anyhow::Result<()> {
|
||||||
jvm.invoke_static(
|
if let Err(e) = env.call_static_method(
|
||||||
"org.patchbukkit.PatchBukkitServer",
|
jni::jni_str!("org/patchbukkit/PatchBukkitServer"),
|
||||||
"initServer",
|
jni::jni_str!("initServer"),
|
||||||
InvocationArg::empty(),
|
jni::jni_sig!("()Lorg/patchbukkit/PatchBukkitServer;"),
|
||||||
)?;
|
&[],
|
||||||
|
) {
|
||||||
|
env.exception_describe();
|
||||||
|
env.exception_clear();
|
||||||
|
return Err(anyhow::anyhow!("Failed to setup PatchBukkit server: {e:?}"));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +1,62 @@
|
|||||||
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player};
|
||||||
use crate::proto::patchbukkit::{
|
use crate::proto::patchbukkit::{
|
||||||
abilities::{Abilities, SetAbilitiesRequest},
|
abilities::{Abilities, SetAbilitiesRequest},
|
||||||
common::Uuid,
|
common::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
|
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
with_player(Some(&request), |player| {
|
||||||
let player_uuid = uuid::Uuid::parse_str(&request.value).ok()?;
|
let abilities = match player.abilities.try_lock() {
|
||||||
let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?;
|
Ok(guard) => Abilities {
|
||||||
let abilities = tokio::task::block_in_place(|| {
|
invulnerable: guard.invulnerable,
|
||||||
ctx.runtime
|
flying: guard.flying,
|
||||||
.block_on(async { player.abilities.lock().await })
|
allow_flying: guard.allow_flying,
|
||||||
|
creative: guard.creative,
|
||||||
|
allow_modify_world: guard.allow_modify_world,
|
||||||
|
fly_speed: guard.fly_speed,
|
||||||
|
walk_speed: guard.walk_speed,
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let mut acquired = None;
|
||||||
|
for _ in 0..10 {
|
||||||
|
std::thread::yield_now();
|
||||||
|
if let Ok(guard) = player.abilities.try_lock() {
|
||||||
|
acquired = Some(Abilities {
|
||||||
|
invulnerable: guard.invulnerable,
|
||||||
|
flying: guard.flying,
|
||||||
|
allow_flying: guard.allow_flying,
|
||||||
|
creative: guard.creative,
|
||||||
|
allow_modify_world: guard.allow_modify_world,
|
||||||
|
fly_speed: guard.fly_speed,
|
||||||
|
walk_speed: guard.walk_speed,
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
Some(Abilities {
|
}
|
||||||
invulnerable: abilities.invulnerable,
|
}
|
||||||
flying: abilities.flying,
|
acquired.unwrap_or(Abilities {
|
||||||
allow_flying: abilities.allow_flying,
|
invulnerable: false,
|
||||||
creative: abilities.creative,
|
flying: false,
|
||||||
allow_modify_world: abilities.allow_modify_world,
|
allow_flying: false,
|
||||||
fly_speed: abilities.fly_speed,
|
creative: false,
|
||||||
walk_speed: abilities.walk_speed,
|
allow_modify_world: true,
|
||||||
|
fly_speed: 0.05,
|
||||||
|
walk_speed: 0.1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(abilities)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
|
pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
let player_uuid = uuid::Uuid::parse_str(&request.uuid?.value).ok()?;
|
|
||||||
let abilities = request.abilities?;
|
let abilities = request.abilities?;
|
||||||
let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?;
|
with_player(request.uuid.as_ref(), |player| {
|
||||||
|
let player = player.clone();
|
||||||
let mut pumpkin_abilities = tokio::task::block_in_place(|| {
|
ctx.runtime.spawn(async move {
|
||||||
ctx.runtime
|
{
|
||||||
.block_on(async { player.abilities.lock().await })
|
let mut pumpkin_abilities = player.abilities.lock().await;
|
||||||
});
|
|
||||||
|
|
||||||
pumpkin_abilities.invulnerable = abilities.invulnerable;
|
pumpkin_abilities.invulnerable = abilities.invulnerable;
|
||||||
pumpkin_abilities.flying = abilities.flying;
|
pumpkin_abilities.flying = abilities.flying;
|
||||||
pumpkin_abilities.allow_flying = abilities.allow_flying;
|
pumpkin_abilities.allow_flying = abilities.allow_flying;
|
||||||
@ -42,11 +64,10 @@ pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Opt
|
|||||||
pumpkin_abilities.allow_modify_world = abilities.allow_modify_world;
|
pumpkin_abilities.allow_modify_world = abilities.allow_modify_world;
|
||||||
pumpkin_abilities.fly_speed = abilities.fly_speed;
|
pumpkin_abilities.fly_speed = abilities.fly_speed;
|
||||||
pumpkin_abilities.walk_speed = abilities.walk_speed;
|
pumpkin_abilities.walk_speed = abilities.walk_speed;
|
||||||
|
}
|
||||||
let player = player.clone();
|
|
||||||
ctx.runtime.spawn(async move {
|
|
||||||
player.send_abilities_update().await;
|
player.send_abilities_update().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(true)
|
Some(true)
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,13 @@ use pumpkin_util::permission::{Permission, PermissionDefault};
|
|||||||
use crate::{commands::init_java_command, proto::patchbukkit::command::RegisterCommandRequest};
|
use crate::{commands::init_java_command, proto::patchbukkit::command::RegisterCommandRequest};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest) -> Option<()> {
|
pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest) -> Option<()> {
|
||||||
let context = super::CALLBACK_CONTEXT.get()?;
|
let Some(context) = super::CALLBACK_CONTEXT.get() else {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to register command '{}': CALLBACK_CONTEXT is not initialized",
|
||||||
|
request.cmd_name
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let cmd_name = request.cmd_name;
|
let cmd_name = request.cmd_name;
|
||||||
let aliases = request.aliases;
|
let aliases = request.aliases;
|
||||||
@ -12,6 +18,13 @@ pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest)
|
|||||||
let command_tx = context.command_tx.clone();
|
let command_tx = context.command_tx.clone();
|
||||||
let plugin_context = context.plugin_context.clone();
|
let plugin_context = context.plugin_context.clone();
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Registering Bukkit command '{}' from plugin '{}' (aliases: {:?})",
|
||||||
|
cmd_name,
|
||||||
|
plugin_name,
|
||||||
|
aliases
|
||||||
|
);
|
||||||
|
|
||||||
context.runtime.spawn(async move {
|
context.runtime.spawn(async move {
|
||||||
let mut raw_names: Vec<String> = vec![cmd_name.clone()];
|
let mut raw_names: Vec<String> = vec![cmd_name.clone()];
|
||||||
raw_names.extend(aliases.clone());
|
raw_names.extend(aliases.clone());
|
||||||
@ -34,23 +47,31 @@ pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = init_java_command(cmd_name.clone(), command_tx, names, description);
|
let node = init_java_command(cmd_name.clone(), command_tx, names.clone(), description);
|
||||||
|
|
||||||
let clean_perm = cmd_name.trim_start_matches('/');
|
let clean_perm = cmd_name.trim_start_matches('/');
|
||||||
let permission = format!("patchbukkit:command.{clean_perm}");
|
let permission = format!("patchbukkit:command.{clean_perm}");
|
||||||
|
|
||||||
let registry = {
|
let registry = plugin_context.server.permission_manager.clone();
|
||||||
let permission_manager = plugin_context.permission_manager.read().await;
|
|
||||||
permission_manager.registry.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = registry.write().await.register_permission(Permission::new(
|
if let Err(e) = registry.register_permission(Permission::new(
|
||||||
&permission,
|
&permission,
|
||||||
&format!("Allows running command `{cmd_name}` from `{plugin_name}`"),
|
&format!("Allows running command `{cmd_name}` from `{plugin_name}`"),
|
||||||
PermissionDefault::Allow,
|
PermissionDefault::Allow,
|
||||||
));
|
)) {
|
||||||
|
tracing::debug!("Permission '{}' registration notice: {}", permission, e);
|
||||||
|
}
|
||||||
|
|
||||||
plugin_context.register_command(node, permission).await;
|
plugin_context
|
||||||
|
.register_command(node, permission.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Successfully registered command '{}' with names {:?} and permission '{}'",
|
||||||
|
cmd_name,
|
||||||
|
names,
|
||||||
|
permission
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
|
|||||||
@ -12,12 +12,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_get_entity_health_impl(request: Uuid) -> Option<EntityHealthResponse> {
|
pub fn ffi_native_bridge_get_entity_health_impl(request: Uuid) -> Option<EntityHealthResponse> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
|
||||||
with_player(Some(&request), |player| {
|
with_player(Some(&request), |player| {
|
||||||
let health = tokio::task::block_in_place(|| {
|
let health = f64::from(player.living_entity.health.load());
|
||||||
ctx.runtime
|
|
||||||
.block_on(async { f64::from(player.living_entity.health.load()) })
|
|
||||||
});
|
|
||||||
|
|
||||||
EntityHealthResponse {
|
EntityHealthResponse {
|
||||||
health,
|
health,
|
||||||
@ -155,24 +151,22 @@ pub fn ffi_native_bridge_is_op_impl(
|
|||||||
let op_lvl = ctx.plugin_context.server.basic_config.op_permission_level;
|
let op_lvl = ctx.plugin_context.server.basic_config.op_permission_level;
|
||||||
let mut is_op = perm_lvl >= op_lvl || perm_lvl > pumpkin_util::PermissionLvl::Zero;
|
let mut is_op = perm_lvl >= op_lvl || perm_lvl > pumpkin_util::PermissionLvl::Zero;
|
||||||
|
|
||||||
if !is_op {
|
if !is_op
|
||||||
let op_config = tokio::task::block_in_place(|| {
|
&& let Ok(op_config) = ctx.plugin_context.server.data.operator_config.try_read()
|
||||||
ctx.runtime
|
&& op_config.get_entry(&player_uuid).is_some()
|
||||||
.block_on(async { ctx.plugin_context.server.data.operator_config.read().await })
|
{
|
||||||
});
|
|
||||||
if op_config.get_entry(&player_uuid).is_some() {
|
|
||||||
is_op = true;
|
is_op = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is_op
|
is_op
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
let op_config = tokio::task::block_in_place(|| {
|
ctx.plugin_context
|
||||||
ctx.runtime
|
.server
|
||||||
.block_on(async { ctx.plugin_context.server.data.operator_config.read().await })
|
.data
|
||||||
});
|
.operator_config
|
||||||
op_config.get_entry(&player_uuid).is_some()
|
.try_read()
|
||||||
|
.is_ok_and(|ops| ops.get_entry(&player_uuid).is_some())
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(crate::proto::patchbukkit::entity::IsOpResponse { is_op })
|
Some(crate::proto::patchbukkit::entity::IsOpResponse { is_op })
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin::plugin::EventPriority;
|
use pumpkin::plugin::EventPriority;
|
||||||
use pumpkin::plugin::player::player_join::PlayerJoinEvent;
|
|
||||||
use pumpkin_util::text::TextComponent;
|
|
||||||
|
|
||||||
use crate::events::handler::PatchBukkitEventHandler;
|
use crate::events::handler::PatchBukkitEventHandler;
|
||||||
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
||||||
use crate::proto::patchbukkit::events::event::Data;
|
|
||||||
use crate::proto::patchbukkit::events::{
|
use crate::proto::patchbukkit::events::{
|
||||||
CallEventRequest, CallEventResponse, RegisterEventRequest,
|
CallEventRequest, CallEventResponse, RegisterEventRequest,
|
||||||
};
|
};
|
||||||
@ -32,8 +29,6 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
let command_tx = ctx.command_tx.clone();
|
let command_tx = ctx.command_tx.clone();
|
||||||
let context = ctx.plugin_context.clone();
|
let context = ctx.plugin_context.clone();
|
||||||
|
|
||||||
tokio::task::block_in_place(|| {
|
|
||||||
ctx.runtime.block_on(async {
|
|
||||||
match request.event_type.as_str() {
|
match request.event_type.as_str() {
|
||||||
"org.bukkit.event.player.PlayerJoinEvent" => {
|
"org.bukkit.event.player.PlayerJoinEvent" => {
|
||||||
context
|
context
|
||||||
@ -47,8 +42,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerQuitEvent" => {
|
"org.bukkit.event.player.PlayerQuitEvent" => {
|
||||||
context
|
context
|
||||||
@ -62,8 +56,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerGameModeChangeEvent" => {
|
"org.bukkit.event.player.PlayerGameModeChangeEvent" => {
|
||||||
context
|
context
|
||||||
@ -77,8 +70,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerInteractEvent" => {
|
"org.bukkit.event.player.PlayerInteractEvent" => {
|
||||||
context
|
context
|
||||||
@ -92,8 +84,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockBreakEvent" => {
|
"org.bukkit.event.block.BlockBreakEvent" => {
|
||||||
context
|
context
|
||||||
@ -107,8 +98,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockPlaceEvent" => {
|
"org.bukkit.event.block.BlockPlaceEvent" => {
|
||||||
context
|
context
|
||||||
@ -122,8 +112,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.SignChangeEvent" => {
|
"org.bukkit.event.block.SignChangeEvent" => {
|
||||||
context
|
context
|
||||||
@ -137,8 +126,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockDamageEvent" => {
|
"org.bukkit.event.block.BlockDamageEvent" => {
|
||||||
context
|
context
|
||||||
@ -152,8 +140,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.server.ServerCommandEvent" => {
|
"org.bukkit.event.server.ServerCommandEvent" => {
|
||||||
context
|
context
|
||||||
@ -167,8 +154,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockIgniteEvent" => {
|
"org.bukkit.event.block.BlockIgniteEvent" => {
|
||||||
context
|
context
|
||||||
@ -182,8 +168,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockGrowEvent" => {
|
"org.bukkit.event.block.BlockGrowEvent" => {
|
||||||
context
|
context
|
||||||
@ -197,8 +182,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockFormEvent" => {
|
"org.bukkit.event.block.BlockFormEvent" => {
|
||||||
context
|
context
|
||||||
@ -212,8 +196,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.block.BlockFadeEvent" => {
|
"org.bukkit.event.block.BlockFadeEvent" => {
|
||||||
context
|
context
|
||||||
@ -227,8 +210,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerToggleSneakEvent" => {
|
"org.bukkit.event.player.PlayerToggleSneakEvent" => {
|
||||||
context
|
context
|
||||||
@ -242,8 +224,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerToggleSprintEvent" => {
|
"org.bukkit.event.player.PlayerToggleSprintEvent" => {
|
||||||
context
|
context
|
||||||
@ -257,8 +238,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerToggleFlightEvent" => {
|
"org.bukkit.event.player.PlayerToggleFlightEvent" => {
|
||||||
context
|
context
|
||||||
@ -272,8 +252,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerMoveEvent" => {
|
"org.bukkit.event.player.PlayerMoveEvent" => {
|
||||||
context
|
context
|
||||||
@ -287,10 +266,12 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerInteractEntityEvent" | "org.bukkit.event.player.PlayerInteractAtEntityEvent" | "org.bukkit.event.entity.EntityDamageByEntityEvent" | "org.bukkit.event.entity.EntityDamageEvent" => {
|
"org.bukkit.event.player.PlayerInteractEntityEvent"
|
||||||
|
| "org.bukkit.event.player.PlayerInteractAtEntityEvent"
|
||||||
|
| "org.bukkit.event.entity.EntityDamageByEntityEvent"
|
||||||
|
| "org.bukkit.event.entity.EntityDamageEvent" => {
|
||||||
context
|
context
|
||||||
.register_event::<
|
.register_event::<
|
||||||
pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent,
|
pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent,
|
||||||
@ -302,10 +283,10 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.AsyncPlayerChatEvent" | "org.bukkit.event.player.PlayerChatEvent" => {
|
"org.bukkit.event.player.AsyncPlayerChatEvent"
|
||||||
|
| "org.bukkit.event.player.PlayerChatEvent" => {
|
||||||
context
|
context
|
||||||
.register_event::<
|
.register_event::<
|
||||||
pumpkin::plugin::player::player_chat::PlayerChatEvent,
|
pumpkin::plugin::player::player_chat::PlayerChatEvent,
|
||||||
@ -317,8 +298,7 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
)),
|
)),
|
||||||
pumpkin_priority,
|
pumpkin_priority,
|
||||||
request.blocking,
|
request.blocking,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
// Player Events
|
// Player Events
|
||||||
"org.bukkit.event.player.PlayerDropItemEvent"
|
"org.bukkit.event.player.PlayerDropItemEvent"
|
||||||
@ -429,383 +409,15 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
_ => {
|
_ => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Unsupported Bukkit event type '{}' from plugin '{}'",
|
"Unsupported Bukkit event type '{}' from plugin '{}'",
|
||||||
request.event_type, request.plugin_name
|
request.event_type,
|
||||||
|
request.plugin_name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option<CallEventResponse> {
|
pub fn ffi_native_bridge_call_event_impl(_request: CallEventRequest) -> Option<CallEventResponse> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
Some(CallEventResponse { handled: false })
|
||||||
let event = request.event?;
|
|
||||||
tracing::debug!("Java calling event {:?}", event);
|
|
||||||
|
|
||||||
let context = ctx.plugin_context.clone();
|
|
||||||
|
|
||||||
let handled = tokio::task::block_in_place(|| {
|
|
||||||
ctx.runtime.block_on(async {
|
|
||||||
match event.data? {
|
|
||||||
Data::PlayerJoin(player_join_event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&player_join_event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = PlayerJoinEvent::new(
|
|
||||||
player,
|
|
||||||
TextComponent::from_legacy_string(&player_join_event_data.join_message),
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerQuit(player_quit_event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&player_quit_event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_leave::PlayerLeaveEvent::new(
|
|
||||||
player,
|
|
||||||
TextComponent::from_legacy_string(&player_quit_event_data.quit_message),
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerGamemodeChange(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let new_gamemode = match event_data.new_gamemode.to_lowercase().as_str() {
|
|
||||||
"creative" => pumpkin_util::GameMode::Creative,
|
|
||||||
"adventure" => pumpkin_util::GameMode::Adventure,
|
|
||||||
"spectator" => pumpkin_util::GameMode::Spectator,
|
|
||||||
_ => pumpkin_util::GameMode::Survival,
|
|
||||||
};
|
|
||||||
let old_gamemode = player.gamemode.load();
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent::new(
|
|
||||||
player,
|
|
||||||
old_gamemode,
|
|
||||||
new_gamemode,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerInteract(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let action = match event_data.action.as_str() {
|
|
||||||
"LEFT_CLICK_BLOCK" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickBlock,
|
|
||||||
"LEFT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickAir,
|
|
||||||
"RIGHT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickAir,
|
|
||||||
_ => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickBlock,
|
|
||||||
};
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.clicked_x, event_data.clicked_y, event_data.clicked_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_interact_event::PlayerInteractEvent::new(
|
|
||||||
&player,
|
|
||||||
action,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
Some(block_pos),
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockBreak(event_data) => {
|
|
||||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
|
||||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
|
||||||
context.server.get_player_by_uuid(uuid)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_break::BlockBreakEvent::new(
|
|
||||||
player,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
block_pos,
|
|
||||||
event_data.exp,
|
|
||||||
event_data.drop_items,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockPlace(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_place::BlockPlaceEvent::new(
|
|
||||||
player,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
block_pos,
|
|
||||||
event_data.can_build,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerToggleSneak(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent::new(
|
|
||||||
player,
|
|
||||||
event_data.is_sneaking,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerToggleSprint(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent::new(
|
|
||||||
player,
|
|
||||||
event_data.is_sprinting,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerToggleFlight(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent::new(
|
|
||||||
player,
|
|
||||||
event_data.is_flying,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerMove(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let from = event_data
|
|
||||||
.from
|
|
||||||
.and_then(|l| l.position)
|
|
||||||
.map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let to = event_data
|
|
||||||
.to
|
|
||||||
.and_then(|l| l.position)
|
|
||||||
.map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_move::PlayerMoveEvent::new(
|
|
||||||
player, from, to,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerInteractEntity(event_data) => {
|
|
||||||
let player_uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(player_uuid)?;
|
|
||||||
let target_uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.target_uuid?.value).ok()?;
|
|
||||||
let target = context.server.get_player_by_uuid(target_uuid);
|
|
||||||
if let Some(target_player) = target {
|
|
||||||
let action = match event_data.action.as_str() {
|
|
||||||
"ATTACK" => pumpkin_protocol::java::server::play::ActionType::Attack,
|
|
||||||
"INTERACT_AT" => pumpkin_protocol::java::server::play::ActionType::InteractAt,
|
|
||||||
_ => pumpkin_protocol::java::server::play::ActionType::Interact,
|
|
||||||
};
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent::new(
|
|
||||||
&player,
|
|
||||||
target_player,
|
|
||||||
action,
|
|
||||||
None,
|
|
||||||
event_data.is_sneaking,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PlayerChat(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::player::player_chat::PlayerChatEvent::new(
|
|
||||||
player,
|
|
||||||
event_data.message,
|
|
||||||
Vec::new(),
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::SignChange(event_data) => {
|
|
||||||
let uuid =
|
|
||||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
|
||||||
let player = context.server.get_player_by_uuid(uuid)?;
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::sign_change::SignChangeEvent::new(
|
|
||||||
player,
|
|
||||||
block_pos,
|
|
||||||
event_data.lines,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockDamage(event_data) => {
|
|
||||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
|
||||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
|
||||||
context.server.get_player_by_uuid(uuid)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(player) = player {
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_damage::BlockDamageEvent::new(
|
|
||||||
player,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
block_pos,
|
|
||||||
event_data.insta_break,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::ServerCommand(event_data) => {
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::server::server_command::ServerCommandEvent::new(
|
|
||||||
event_data.command,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockIgnite(event_data) => {
|
|
||||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
|
||||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
|
||||||
context.server.get_player_by_uuid(uuid)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_ignite::BlockIgniteEvent::new(
|
|
||||||
block_pos,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
player,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockGrow(event_data) => {
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let world = context.server.worlds.load().first().cloned()?;
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_grow::BlockGrowEvent::new(
|
|
||||||
world,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
pumpkin_data::BlockStateId::new_or_air(0),
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
pumpkin_data::BlockStateId::new_or_air(0),
|
|
||||||
block_pos,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockForm(event_data) => {
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_form::BlockFormEvent::new(
|
|
||||||
block_pos,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::BlockFade(event_data) => {
|
|
||||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
|
||||||
let mut pumpkin_event = pumpkin::plugin::block::block_fade::BlockFadeEvent::new(
|
|
||||||
block_pos,
|
|
||||||
&pumpkin_data::Block::AIR,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.server
|
|
||||||
.plugin_manager
|
|
||||||
.fire(&context.server, &mut pumpkin_event)
|
|
||||||
.await;
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PluginEnable(plugin_enable_event) => {
|
|
||||||
tracing::info!("Plugin enable event for {}", plugin_enable_event.plugin_name);
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
Data::PluginDisable(plugin_disable_event) => {
|
|
||||||
tracing::info!("Plugin disable event for {}", plugin_disable_event.plugin_name);
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Some(CallEventResponse { handled })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack as PumpkinItemStack;
|
use pumpkin_data::item_stack::ItemStack as PumpkinItemStack;
|
||||||
use tokio::task::block_in_place;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player},
|
java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player},
|
||||||
@ -55,22 +54,34 @@ fn proto_item_to_pumpkin(proto: Option<&ProtoItemStack>) -> PumpkinItemStack {
|
|||||||
pub fn ffi_native_bridge_get_player_inventory_impl(
|
pub fn ffi_native_bridge_get_player_inventory_impl(
|
||||||
request: Uuid,
|
request: Uuid,
|
||||||
) -> Option<GetPlayerInventoryResponse> {
|
) -> Option<GetPlayerInventoryResponse> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
|
||||||
with_player(Some(&request), |player| {
|
with_player(Some(&request), |player| {
|
||||||
let player = player.clone();
|
|
||||||
block_in_place(|| {
|
|
||||||
ctx.runtime.block_on(async {
|
|
||||||
let selected_slot = u32::from(player.inventory.get_selected_slot());
|
let selected_slot = u32::from(player.inventory.get_selected_slot());
|
||||||
|
|
||||||
let main_guard = player.inventory.main_inventory.read().await;
|
let main_inventory = player
|
||||||
let main_inventory = main_guard.iter().map(pumpkin_item_to_proto).collect();
|
.inventory
|
||||||
|
.main_inventory
|
||||||
|
.try_read()
|
||||||
|
.map(|main_guard| main_guard.iter().map(pumpkin_item_to_proto).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let eq_guard = player.inventory.entity_equipment.lock().await;
|
let (off_hand, helmet, chestplate, leggings, boots) =
|
||||||
let off_hand = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::OFF_HAND));
|
if let Ok(eq_guard) = player.inventory.entity_equipment.try_lock() {
|
||||||
let helmet = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::HEAD));
|
(
|
||||||
let chestplate = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::CHEST));
|
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::OFF_HAND)),
|
||||||
let leggings = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::LEGS));
|
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::HEAD)),
|
||||||
let boots = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::FEET));
|
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::CHEST)),
|
||||||
|
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::LEGS)),
|
||||||
|
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::FEET)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
pumpkin_item_to_proto(PumpkinItemStack::EMPTY),
|
||||||
|
pumpkin_item_to_proto(PumpkinItemStack::EMPTY),
|
||||||
|
pumpkin_item_to_proto(PumpkinItemStack::EMPTY),
|
||||||
|
pumpkin_item_to_proto(PumpkinItemStack::EMPTY),
|
||||||
|
pumpkin_item_to_proto(PumpkinItemStack::EMPTY),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
GetPlayerInventoryResponse {
|
GetPlayerInventoryResponse {
|
||||||
main_inventory,
|
main_inventory,
|
||||||
@ -82,8 +93,6 @@ pub fn ffi_native_bridge_get_player_inventory_impl(
|
|||||||
boots: Some(boots),
|
boots: Some(boots),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ffi_native_bridge_set_player_inventory_slot_impl(
|
pub fn ffi_native_bridge_set_player_inventory_slot_impl(
|
||||||
|
|||||||
@ -4,12 +4,17 @@ pub fn ffi_native_bridge_send_log_impl(request: SendLogRequest) -> Option<()> {
|
|||||||
let logger = &request.logger_name;
|
let logger = &request.logger_name;
|
||||||
let msg = &request.message;
|
let msg = &request.message;
|
||||||
|
|
||||||
|
// Filter internal JDK / system loggers
|
||||||
|
if logger.starts_with("jdk.") || logger.starts_with("sun.") || logger.starts_with("javax.") {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
|
||||||
match request.level() {
|
match request.level() {
|
||||||
LogLevel::Severe => tracing::error!(logger = %logger, "{}", msg),
|
LogLevel::Severe => tracing::error!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Warning => tracing::warn!(logger = %logger, "{}", msg),
|
LogLevel::Warning => tracing::warn!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Info => tracing::info!(logger = %logger, "{}", msg),
|
LogLevel::Info => tracing::info!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Config => tracing::debug!(logger = %logger, "{}", msg),
|
LogLevel::Config => tracing::debug!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Fine => tracing::debug!(logger = %logger, "{}", msg),
|
LogLevel::Fine => tracing::trace!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Finer => tracing::trace!(logger = %logger, "{}", msg),
|
LogLevel::Finer => tracing::trace!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Finest => tracing::trace!(logger = %logger, "{}", msg),
|
LogLevel::Finest => tracing::trace!(logger = %logger, "{}", msg),
|
||||||
LogLevel::Off => {}
|
LogLevel::Off => {}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::Jvm;
|
|
||||||
use pumpkin::plugin::Context;
|
use pumpkin::plugin::Context;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@ -76,6 +75,6 @@ pub fn init_callback_context(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
pub fn initialize_callbacks(env: &mut jni::Env) -> Result<()> {
|
||||||
initialize_ffi_callbacks(jvm)
|
initialize_ffi_callbacks(env)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,8 +38,7 @@ pub fn ffi_native_bridge_player_entity_play_sound_impl(
|
|||||||
let entity = entity.first()?.get_entity();
|
let entity = entity.first()?.get_entity();
|
||||||
|
|
||||||
player
|
player
|
||||||
.client
|
.send_client_packet(&CEntitySoundEffect::new(
|
||||||
.enqueue_packet(&CEntitySoundEffect::new(
|
|
||||||
IdOr::Id(pumpkin_sound as u16),
|
IdOr::Id(pumpkin_sound as u16),
|
||||||
category,
|
category,
|
||||||
entity.entity_id.into(),
|
entity.entity_id.into(),
|
||||||
|
|||||||
@ -12,6 +12,14 @@ pub fn get_string(str_ptr: *const c_char) -> String {
|
|||||||
unsafe { CStr::from_ptr(str_ptr).to_string_lossy().into_owned() }
|
unsafe { CStr::from_ptr(str_ptr).to_string_lossy().into_owned() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cache_player(player: Arc<Player>) {
|
||||||
|
let player_uuid = player.gameprofile.id;
|
||||||
|
if let Ok(mut write_guard) = PLAYER_HANDLE_CACHE.write() {
|
||||||
|
let cache = write_guard.get_or_insert_with(HashMap::new);
|
||||||
|
cache.insert(player_uuid, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_player<F, R>(proto_uuid: Option<&ProtoUuid>, f: F) -> Option<R>
|
pub fn with_player<F, R>(proto_uuid: Option<&ProtoUuid>, f: F) -> Option<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(Arc<Player>) -> R,
|
F: FnOnce(Arc<Player>) -> R,
|
||||||
|
|||||||
@ -100,7 +100,7 @@ pub fn ffi_native_bridge_get_world_border_impl(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| worlds.first().cloned())?;
|
.or_else(|| worlds.first().cloned())?;
|
||||||
|
|
||||||
let wb = world.worldborder.blocking_lock();
|
let wb = world.worldborder.try_lock().ok()?;
|
||||||
|
|
||||||
Some(crate::proto::patchbukkit::world::WorldBorderData {
|
Some(crate::proto::patchbukkit::world::WorldBorderData {
|
||||||
center_x: wb.center_x,
|
center_x: wb.center_x,
|
||||||
@ -131,7 +131,8 @@ pub fn ffi_native_bridge_set_world_border_impl(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| worlds.first().cloned())?;
|
.or_else(|| worlds.first().cloned())?;
|
||||||
|
|
||||||
let mut wb = world.worldborder.blocking_lock();
|
ctx.runtime.spawn(async move {
|
||||||
|
let mut wb = world.worldborder.lock().await;
|
||||||
wb.center_x = border_data.center_x;
|
wb.center_x = border_data.center_x;
|
||||||
wb.center_z = border_data.center_z;
|
wb.center_z = border_data.center_z;
|
||||||
wb.old_diameter = border_data.size;
|
wb.old_diameter = border_data.size;
|
||||||
@ -142,6 +143,7 @@ pub fn ffi_native_bridge_set_world_border_impl(
|
|||||||
wb.damage_per_block = border_data.damage_per_block as f32;
|
wb.damage_per_block = border_data.damage_per_block as f32;
|
||||||
wb.buffer = border_data.damage_buffer as f32;
|
wb.buffer = border_data.damage_buffer as f32;
|
||||||
wb.portal_teleport_boundary = border_data.max_center_coordinate;
|
wb.portal_teleport_boundary = border_data.max_center_coordinate;
|
||||||
|
});
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,11 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm};
|
use jni::Env;
|
||||||
use pumpkin::{command::dispatcher::CommandError, plugin::Context};
|
use pumpkin::command::dispatcher::CommandError;
|
||||||
use pumpkin_protocol::java::client::play::CommandSuggestion;
|
use pumpkin_protocol::java::client::play::CommandSuggestion;
|
||||||
use pumpkin_util::permission::{Permission, PermissionDefault};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{commands::SimpleCommandSender, java::jvm::commands::Location};
|
||||||
commands::{SimpleCommandSender, init_java_command},
|
|
||||||
config,
|
|
||||||
java::{
|
|
||||||
jvm::commands::{JvmCommand, Location},
|
|
||||||
plugin::manager::Plugin,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const PATCHBUKKIT_PERMISSION_NAMESPACE: &str = "patchbukkit";
|
pub struct CommandManager;
|
||||||
|
|
||||||
pub struct CommandManager {
|
|
||||||
command_map: Option<Instance>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CommandManager {
|
impl Default for CommandManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -31,39 +16,17 @@ impl Default for CommandManager {
|
|||||||
impl CommandManager {
|
impl CommandManager {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self { command_map: None }
|
Self
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(&mut self, jvm: &Jvm) -> Result<()> {
|
|
||||||
let server_instance =
|
|
||||||
jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?;
|
|
||||||
|
|
||||||
let command_map = jvm.invoke(&server_instance, "getCommandMap", InvocationArg::empty())?;
|
|
||||||
let command_map = jvm.cast(
|
|
||||||
&command_map,
|
|
||||||
"org.patchbukkit.command.PatchBukkitCommandMap",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.command_map = Some(command_map);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_or_init_command_map(&mut self, jvm: &Jvm) -> Result<&Instance> {
|
|
||||||
if self.command_map.is_none() {
|
|
||||||
self.init(jvm)?;
|
|
||||||
}
|
|
||||||
Ok(self.command_map.as_ref().unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tab_complete(
|
pub fn get_tab_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
sender: SimpleCommandSender,
|
sender: SimpleCommandSender,
|
||||||
full_command: String,
|
full_command: String,
|
||||||
location: Option<Location>,
|
location: Option<Location>,
|
||||||
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
|
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
|
||||||
match self.try_tab_complete(jvm, sender, full_command, location) {
|
match self.try_tab_complete(env, sender, full_command, location) {
|
||||||
Ok(suggestions) => Ok(suggestions),
|
Ok(suggestions) => Ok(suggestions),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Tab completion failed: {e}");
|
tracing::warn!("Tab completion failed: {e}");
|
||||||
@ -74,271 +37,106 @@ impl CommandManager {
|
|||||||
|
|
||||||
fn try_tab_complete(
|
fn try_tab_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
sender: SimpleCommandSender,
|
sender: SimpleCommandSender,
|
||||||
full_command: String,
|
full_command: String,
|
||||||
location: Option<Location>,
|
location: Option<Location>,
|
||||||
) -> Result<Option<Vec<CommandSuggestion>>> {
|
) -> Result<Option<Vec<CommandSuggestion>>> {
|
||||||
let command_map = self.get_or_init_command_map(jvm)?;
|
let (uuid_jstr, name_jstr, is_op) = match sender {
|
||||||
|
SimpleCommandSender::Console => (None, None, true),
|
||||||
let sender = Self::sender_to_jsender(jvm, sender)?;
|
SimpleCommandSender::Player(uuid_str, name, is_op) => (
|
||||||
|
Some(env.new_string(&uuid_str)?),
|
||||||
let completions = if let Some(location) = location {
|
Some(env.new_string(&name)?),
|
||||||
let world = jvm.invoke_static(
|
is_op,
|
||||||
"org.patchbukkit.world.PatchBukkitWorld",
|
|
||||||
"getOrCreate",
|
|
||||||
&[InvocationArg::try_from(location.world.to_string())?],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let location = match location.rotation {
|
|
||||||
Some(rotation) => jvm.create_instance(
|
|
||||||
"org.bukkit.Location",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(world)?,
|
|
||||||
InvocationArg::try_from(location.x)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(location.y)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(location.z)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(rotation.yaw)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(rotation.pitch)?.into_primitive()?,
|
|
||||||
],
|
|
||||||
)?,
|
|
||||||
None => jvm.create_instance(
|
|
||||||
"org.bukkit.Location",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(world)?,
|
|
||||||
InvocationArg::try_from(location.x)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(location.y)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(location.z)?.into_primitive()?,
|
|
||||||
],
|
|
||||||
)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
jvm.invoke(
|
|
||||||
command_map,
|
|
||||||
"tabComplete",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(sender)?,
|
|
||||||
InvocationArg::try_from(full_command)?,
|
|
||||||
InvocationArg::try_from(location)?,
|
|
||||||
],
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
jvm.invoke(
|
|
||||||
command_map,
|
|
||||||
"tabComplete",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(sender)?,
|
|
||||||
InvocationArg::try_from(full_command)?,
|
|
||||||
],
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let completions: Vec<String> = jvm.to_rust(completions)?;
|
|
||||||
|
|
||||||
Ok(Some(
|
|
||||||
completions
|
|
||||||
.into_iter()
|
|
||||||
.map(|completion| CommandSuggestion::new(completion, None))
|
|
||||||
.collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn register_command(
|
|
||||||
&mut self,
|
|
||||||
jvm: &Jvm,
|
|
||||||
context: &Arc<Context>,
|
|
||||||
plugin: &Plugin,
|
|
||||||
cmd_name: String,
|
|
||||||
cmd_data: &config::spigot::Command,
|
|
||||||
command_tx: mpsc::Sender<JvmCommand>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let command_map = self.get_or_init_command_map(jvm)?;
|
|
||||||
|
|
||||||
let created_cmd = match jvm.invoke_static(
|
|
||||||
"org.patchbukkit.command.CommandFactory",
|
|
||||||
"create",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(&cmd_name)?,
|
|
||||||
InvocationArg::try_from(&plugin.name)?,
|
|
||||||
],
|
|
||||||
) {
|
|
||||||
Ok(inst) => {
|
|
||||||
if jvm
|
|
||||||
.invoke(&inst, "getName", InvocationArg::empty())
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"Failed to create PluginCommand for {}: Java returned null",
|
|
||||||
cmd_name
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
inst
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Failed to create PluginCommand for {}: {:?}", cmd_name, e);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let j_plugin_cmd = Arc::new(Mutex::new(created_cmd));
|
|
||||||
tracing::info!("Registering Bukkit command: {}", &cmd_name);
|
|
||||||
let cmd_aliases: Vec<String> = cmd_data
|
|
||||||
.aliases
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(Vec::new, |a| a.to_vec());
|
|
||||||
let mut raw_names: Vec<String> = vec![cmd_name.clone()];
|
|
||||||
raw_names.extend(cmd_aliases.clone());
|
|
||||||
|
|
||||||
let mut names: Vec<String> = Vec::new();
|
|
||||||
for name in raw_names {
|
|
||||||
let clean = name.trim_start_matches('/').to_string();
|
|
||||||
if !clean.is_empty() {
|
|
||||||
if !names.contains(&clean) {
|
|
||||||
names.push(clean.clone());
|
|
||||||
}
|
|
||||||
let single = format!("/{}", clean);
|
|
||||||
if !names.contains(&single) {
|
|
||||||
names.push(single);
|
|
||||||
}
|
|
||||||
let double = format!("//{}", clean);
|
|
||||||
if !names.contains(&double) {
|
|
||||||
names.push(double);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let cmd_lock = j_plugin_cmd.lock().unwrap();
|
|
||||||
let j_plugin_cmd_owned = jvm.clone_instance(&cmd_lock)?;
|
|
||||||
|
|
||||||
if let Some(ref desc) = cmd_data.description {
|
|
||||||
let _ = jvm.invoke(
|
|
||||||
&j_plugin_cmd_owned,
|
|
||||||
"setDescription",
|
|
||||||
&[InvocationArg::try_from(desc)?],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(ref usage) = cmd_data.usage {
|
|
||||||
let _ = jvm.invoke(
|
|
||||||
&j_plugin_cmd_owned,
|
|
||||||
"setUsage",
|
|
||||||
&[InvocationArg::try_from(usage)?],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(ref perm_msg) = cmd_data.permission_message {
|
|
||||||
let _ = jvm.invoke(
|
|
||||||
&j_plugin_cmd_owned,
|
|
||||||
"setPermissionMessage",
|
|
||||||
&[InvocationArg::try_from(perm_msg)?],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(ref perm) = cmd_data.permission {
|
|
||||||
let _ = jvm.invoke(
|
|
||||||
&j_plugin_cmd_owned,
|
|
||||||
"setPermission",
|
|
||||||
&[InvocationArg::try_from(perm)?],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if !cmd_aliases.is_empty() {
|
|
||||||
let j_aliases = jvm.create_java_array(
|
|
||||||
"java.lang.String",
|
|
||||||
&cmd_aliases
|
|
||||||
.iter()
|
|
||||||
.map(|a| InvocationArg::try_from(a).unwrap())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)?;
|
|
||||||
let j_list = jvm.invoke_static(
|
|
||||||
"java.util.Arrays",
|
|
||||||
"asList",
|
|
||||||
&[InvocationArg::from(j_aliases)],
|
|
||||||
)?;
|
|
||||||
let _ = jvm.invoke(
|
|
||||||
&j_plugin_cmd_owned,
|
|
||||||
"setAliases",
|
|
||||||
&[InvocationArg::from(j_list)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let j_command_cast = match jvm.cast(&j_plugin_cmd_owned, "org.bukkit.command.Command") {
|
|
||||||
Ok(casted) => casted,
|
|
||||||
Err(_) => j_plugin_cmd_owned,
|
|
||||||
};
|
|
||||||
|
|
||||||
jvm.invoke(
|
|
||||||
command_map,
|
|
||||||
"register",
|
|
||||||
&[
|
|
||||||
InvocationArg::try_from(&cmd_name)?,
|
|
||||||
InvocationArg::try_from(&plugin.name)?,
|
|
||||||
InvocationArg::from(j_command_cast),
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = init_java_command(
|
|
||||||
cmd_name.clone(),
|
|
||||||
command_tx.clone(),
|
|
||||||
names,
|
|
||||||
cmd_data.description.clone().unwrap_or_default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (permission, permission_default) = build_permission_node(&cmd_name, cmd_data);
|
|
||||||
|
|
||||||
let registry = {
|
|
||||||
let permission_manager = context.permission_manager.read().await;
|
|
||||||
permission_manager.registry.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = registry.write().await.register_permission(Permission::new(
|
|
||||||
&permission,
|
|
||||||
&format!(
|
|
||||||
"Allows running the Bukkit command `{cmd_name}` from `{}`",
|
|
||||||
plugin.name
|
|
||||||
),
|
),
|
||||||
permission_default,
|
};
|
||||||
)) {
|
|
||||||
if e.contains("already registered") {
|
let cmd_jstr = env.new_string(&full_command)?;
|
||||||
tracing::debug!(
|
let (world_jstr, x, y, z) = match location {
|
||||||
"Permission already registered for command {}: {}",
|
Some(loc) => (
|
||||||
cmd_name,
|
Some(env.new_string(loc.world.to_string())?),
|
||||||
e
|
loc.x,
|
||||||
);
|
loc.y,
|
||||||
} else {
|
loc.z,
|
||||||
tracing::warn!(
|
),
|
||||||
"Failed to register permission for command {}: {:?}",
|
None => (None, 0.0, 0.0, 0.0),
|
||||||
cmd_name,
|
};
|
||||||
e
|
|
||||||
);
|
let null_obj = jni::objects::JObject::null();
|
||||||
|
let uuid_val = uuid_jstr.as_deref().unwrap_or(&null_obj).into();
|
||||||
|
let name_val = name_jstr.as_deref().unwrap_or(&null_obj).into();
|
||||||
|
let world_val = world_jstr.as_deref().unwrap_or(&null_obj).into();
|
||||||
|
|
||||||
|
let result = env.call_static_method(
|
||||||
|
jni::jni_str!("org/patchbukkit/command/PatchBukkitCommandMap"),
|
||||||
|
jni::jni_str!("tabCompleteRaw"),
|
||||||
|
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;DDD)[Ljava/lang/String;"),
|
||||||
|
&[
|
||||||
|
uuid_val,
|
||||||
|
name_val,
|
||||||
|
jni::objects::JValue::Bool(is_op),
|
||||||
|
(&cmd_jstr).into(),
|
||||||
|
world_val,
|
||||||
|
jni::objects::JValue::Double(x),
|
||||||
|
jni::objects::JValue::Double(y),
|
||||||
|
jni::objects::JValue::Double(z),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let obj = result.l()?;
|
||||||
|
if obj.is_null() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let array = jni::objects::JObjectArray::<jni::objects::JString>::cast_local(env, obj)?;
|
||||||
|
let len = array.len(env)?;
|
||||||
|
let mut suggestions = Vec::with_capacity(len);
|
||||||
|
for i in 0..len {
|
||||||
|
let elem = array.get_element(env, i)?;
|
||||||
|
if !elem.is_null() {
|
||||||
|
let rust_str = elem.try_to_string(env)?;
|
||||||
|
suggestions.push(CommandSuggestion::new(rust_str, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.register_command(node, permission).await;
|
Ok(Some(suggestions))
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger_command(
|
pub fn trigger_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
full_command: String,
|
full_command: String,
|
||||||
sender: SimpleCommandSender,
|
sender: SimpleCommandSender,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let command_map = self.get_or_init_command_map(jvm)?;
|
let (uuid_jstr, name_jstr, is_op) = match sender {
|
||||||
|
SimpleCommandSender::Console => (None, None, true),
|
||||||
|
SimpleCommandSender::Player(uuid_str, name, is_op) => (
|
||||||
|
Some(env.new_string(&uuid_str)?),
|
||||||
|
Some(env.new_string(&name)?),
|
||||||
|
is_op,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
let j_sender = Self::sender_to_jsender(jvm, sender)?;
|
let cmd_jstr = env.new_string(&full_command)?;
|
||||||
|
let null_obj = jni::objects::JObject::null();
|
||||||
|
let uuid_val = uuid_jstr.as_deref().unwrap_or(&null_obj).into();
|
||||||
|
let name_val = name_jstr.as_deref().unwrap_or(&null_obj).into();
|
||||||
|
|
||||||
let dispatch_result = jvm.invoke(
|
let result = env.call_static_method(
|
||||||
command_map,
|
jni::jni_str!("org/patchbukkit/command/PatchBukkitCommandMap"),
|
||||||
"dispatch",
|
jni::jni_str!("dispatchRaw"),
|
||||||
|
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Z"),
|
||||||
&[
|
&[
|
||||||
InvocationArg::from(j_sender),
|
uuid_val,
|
||||||
InvocationArg::try_from(full_command.clone())?,
|
name_val,
|
||||||
|
jni::objects::JValue::Bool(is_op),
|
||||||
|
(&cmd_jstr).into(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
match dispatch_result {
|
match result {
|
||||||
Ok(result) => {
|
Ok(val) => {
|
||||||
let handled: bool = jvm.to_rust(result)?;
|
let handled = val.z()?;
|
||||||
if !handled {
|
if !handled {
|
||||||
tracing::warn!("Command not handled by any Java plugin: {full_command}");
|
tracing::warn!("Command not handled by any Java plugin: {full_command}");
|
||||||
}
|
}
|
||||||
@ -350,73 +148,16 @@ impl CommandManager {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sender_to_jsender(jvm: &Jvm, sender: SimpleCommandSender) -> Result<Instance> {
|
|
||||||
match sender {
|
|
||||||
SimpleCommandSender::Console => Ok(jvm.invoke_static(
|
|
||||||
"org.bukkit.Bukkit",
|
|
||||||
"getConsoleSender",
|
|
||||||
InvocationArg::empty(),
|
|
||||||
)?),
|
|
||||||
|
|
||||||
SimpleCommandSender::Player(uuid_str, name) => {
|
|
||||||
let server =
|
|
||||||
jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?;
|
|
||||||
let patch_server = jvm.cast(&server, "org.patchbukkit.PatchBukkitServer")?;
|
|
||||||
|
|
||||||
let j_uuid = jvm.invoke_static(
|
|
||||||
"java.util.UUID",
|
|
||||||
"fromString",
|
|
||||||
&[InvocationArg::try_from(uuid_str)?],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let j_player = jvm.invoke(
|
|
||||||
&patch_server,
|
|
||||||
"getPlayer",
|
|
||||||
&[InvocationArg::from(jvm.clone_instance(&j_uuid)?)],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let is_null: bool = jvm.to_rust(jvm.invoke_static(
|
|
||||||
"java.util.Objects",
|
|
||||||
"isNull",
|
|
||||||
&[InvocationArg::from(jvm.clone_instance(&j_player)?)],
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
if is_null {
|
|
||||||
tracing::info!(
|
|
||||||
"Player not found in Java, creating PatchBukkitPlayer for {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
let j_player = jvm.create_instance(
|
|
||||||
"org.patchbukkit.entity.PatchBukkitPlayer",
|
|
||||||
&[InvocationArg::from(j_uuid), InvocationArg::try_from(name)?],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
jvm.invoke(
|
|
||||||
&j_player,
|
|
||||||
"setOp",
|
|
||||||
&[InvocationArg::try_from(true)?.into_primitive()?],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
jvm.invoke(
|
|
||||||
&patch_server,
|
|
||||||
"registerPlayer",
|
|
||||||
&[InvocationArg::from(jvm.clone_instance(&j_player)?)],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(j_player)
|
|
||||||
} else {
|
|
||||||
Ok(j_player)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
const PATCHBUKKIT_PERMISSION_NAMESPACE: &str = "patchbukkit";
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn build_permission_node(
|
fn build_permission_node(
|
||||||
cmd_name: &str,
|
cmd_name: &str,
|
||||||
cmd_data: &config::spigot::Command,
|
cmd_data: &crate::config::spigot::Command,
|
||||||
) -> (String, PermissionDefault) {
|
) -> (String, pumpkin_util::permission::PermissionDefault) {
|
||||||
let clean = cmd_name.trim_start_matches('/');
|
let clean = cmd_name.trim_start_matches('/');
|
||||||
if let Some(permission) = &cmd_data.permission {
|
if let Some(permission) = &cmd_data.permission {
|
||||||
let perm_node = if permission.contains(':') {
|
let perm_node = if permission.contains(':') {
|
||||||
@ -424,11 +165,14 @@ fn build_permission_node(
|
|||||||
} else {
|
} else {
|
||||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:{permission}")
|
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:{permission}")
|
||||||
};
|
};
|
||||||
(perm_node, PermissionDefault::Allow)
|
(
|
||||||
|
perm_node,
|
||||||
|
pumpkin_util::permission::PermissionDefault::Allow,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{clean}"),
|
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{clean}"),
|
||||||
PermissionDefault::Allow,
|
pumpkin_util::permission::PermissionDefault::Allow,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm};
|
use jni::Env;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use pumpkin::{entity::player::Player, server::Server};
|
use pumpkin::{entity::player::Player, server::Server};
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
proto::patchbukkit::events::{FireEventResponse, event::Data},
|
proto::patchbukkit::events::{FireEventResponse, event::Data},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct EventManager {}
|
pub struct EventManager;
|
||||||
|
|
||||||
impl Default for EventManager {
|
impl Default for EventManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -21,144 +21,74 @@ impl Default for EventManager {
|
|||||||
impl EventManager {
|
impl EventManager {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {}
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fire_event(
|
pub fn fire_event(
|
||||||
&self,
|
&self,
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
payload: JvmEventPayload,
|
payload: JvmEventPayload,
|
||||||
plugin_name: String,
|
plugin_name: String,
|
||||||
) -> Result<FireEventResponse> {
|
) -> Result<FireEventResponse> {
|
||||||
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())?;
|
|
||||||
|
|
||||||
if let Some(ref event) = payload.event.data
|
if let Some(ref event) = payload.event.data
|
||||||
&& matches!(event, Data::PlayerJoin(_))
|
&& matches!(event, Data::PlayerJoin(_))
|
||||||
&& let Some(ref player) = payload.context.player
|
&& let Some(ref player) = payload.context.player
|
||||||
{
|
{
|
||||||
Self::register_player(jvm, &patch_server, player, &payload.context.server)?;
|
Self::register_player(env, player, &payload.context.server)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = payload.event.encode_to_vec();
|
let bytes = payload.event.encode_to_vec();
|
||||||
let j_bytes = jvm.create_java_array(
|
let j_bytes = env.byte_array_from_slice(&bytes)?;
|
||||||
"byte",
|
let plugin_name_jstr = env.new_string(&plugin_name)?;
|
||||||
&bytes
|
|
||||||
.iter()
|
let result = env.call_static_method(
|
||||||
.map(|&b| {
|
jni::jni_str!("org/patchbukkit/events/PatchBukkitEventFactory"),
|
||||||
InvocationArg::try_from(b as i8)
|
jni::jni_str!("fireEventFromBytes"),
|
||||||
.unwrap()
|
jni::jni_sig!("([BLjava/lang/String;)[B"),
|
||||||
.into_primitive()
|
&[(&j_bytes).into(), (&plugin_name_jstr).into()],
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let j_event = jvm.invoke_static(
|
let obj = result.l()?;
|
||||||
"org.patchbukkit.events.PatchBukkitEventFactory",
|
if obj.is_null() {
|
||||||
"createEventFromBytes",
|
return Ok(FireEventResponse {
|
||||||
&[InvocationArg::from(j_bytes)],
|
cancelled: false,
|
||||||
)?;
|
data: Some(payload.event),
|
||||||
|
});
|
||||||
let is_null: bool = jvm.to_rust(jvm.invoke_static(
|
|
||||||
"java.util.Objects",
|
|
||||||
"isNull",
|
|
||||||
&[InvocationArg::from(jvm.clone_instance(&j_event)?)],
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
if is_null {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"Failed to create event - factory returned null"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jvm.invoke(
|
let j_byte_array = jni::objects::JByteArray::cast_local(env, obj)?;
|
||||||
&event_manager,
|
let resp_vec = env.convert_byte_array(&j_byte_array)?;
|
||||||
"fireEvent",
|
let response = FireEventResponse::decode(resp_vec.as_slice())?;
|
||||||
&[
|
|
||||||
InvocationArg::from(jvm.clone_instance(&j_event)?),
|
|
||||||
InvocationArg::try_from(plugin_name)?,
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let response_bytes: Vec<i8> = jvm.to_rust(jvm.invoke_static(
|
|
||||||
"org.patchbukkit.events.PatchBukkitEventFactory",
|
|
||||||
"toFireEventResponse",
|
|
||||||
&[InvocationArg::from(j_event)],
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
let response_bytes: Vec<u8> = response_bytes.iter().map(|&b| b as u8).collect();
|
|
||||||
let response = FireEventResponse::decode(response_bytes.as_slice())?;
|
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_player(
|
pub fn register_player(
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
patch_server: &Instance,
|
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
_server: &Arc<Server>,
|
_server: &Arc<Server>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let j_uuid = jvm
|
let player_permission_level = player.permission_lvl.load();
|
||||||
.invoke_static(
|
let is_op = player_permission_level > pumpkin_util::permission::PermissionLvl::Zero
|
||||||
"java.util.UUID",
|
|| _server
|
||||||
"fromString",
|
.data
|
||||||
&[InvocationArg::try_from(player.gameprofile.id.to_string())?],
|
.operator_config
|
||||||
)
|
.try_read()
|
||||||
.map_err(|e| format!("Failed to create Java UUID: {e}"))
|
.is_ok_and(|ops| ops.get_entry(&player.gameprofile.id).is_some());
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let j_player = jvm.create_instance(
|
let uuid_jstr = env.new_string(player.gameprofile.id.to_string())?;
|
||||||
"org.patchbukkit.entity.PatchBukkitPlayer",
|
let name_jstr = env.new_string(&player.gameprofile.name)?;
|
||||||
|
|
||||||
|
env.call_static_method(
|
||||||
|
jni::jni_str!("org/patchbukkit/PatchBukkitServer"),
|
||||||
|
jni::jni_str!("registerPlayer"),
|
||||||
|
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;Z)V"),
|
||||||
&[
|
&[
|
||||||
InvocationArg::from(j_uuid),
|
(&uuid_jstr).into(),
|
||||||
InvocationArg::try_from(player.gameprofile.name.clone())?,
|
(&name_jstr).into(),
|
||||||
|
jni::objects::JValue::Bool(is_op),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let player_permission_level = player.permission_lvl.load();
|
|
||||||
let is_op = player_permission_level > pumpkin_util::permission::PermissionLvl::Zero
|
|
||||||
|| tokio::task::block_in_place(|| {
|
|
||||||
tokio::runtime::Handle::current().block_on(async {
|
|
||||||
_server
|
|
||||||
.data
|
|
||||||
.operator_config
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get_entry(&player.gameprofile.id)
|
|
||||||
.is_some()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_op {
|
|
||||||
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())?;
|
|
||||||
jvm.invoke(
|
|
||||||
&plugin_manager,
|
|
||||||
"callEvent",
|
|
||||||
&[InvocationArg::try_from(event)?],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm};
|
use jni::Env;
|
||||||
use pumpkin::plugin::Context;
|
use pumpkin::plugin::Context;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@ -64,12 +64,9 @@ pub struct Plugin {
|
|||||||
pub state: PluginState,
|
pub state: PluginState,
|
||||||
/// Data folder for this plugin
|
/// Data folder for this plugin
|
||||||
pub data_folder: PathBuf,
|
pub data_folder: PathBuf,
|
||||||
pub instance: Option<Instance>,
|
|
||||||
// The registered commands
|
// The registered commands
|
||||||
pub commands: HashMap<String, Command>,
|
pub commands: HashMap<String, Command>,
|
||||||
|
|
||||||
pub listeners: HashMap<String, Instance>,
|
|
||||||
|
|
||||||
// Dependency metadata (normalized to lowercase)
|
// Dependency metadata (normalized to lowercase)
|
||||||
pub provides: Vec<String>,
|
pub provides: Vec<String>,
|
||||||
pub depends: Vec<String>,
|
pub depends: Vec<String>,
|
||||||
@ -257,9 +254,7 @@ impl PluginManager {
|
|||||||
state: PluginState::Registered,
|
state: PluginState::Registered,
|
||||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||||
path: jar_path.as_ref().to_path_buf(),
|
path: jar_path.as_ref().to_path_buf(),
|
||||||
instance: None,
|
|
||||||
commands,
|
commands,
|
||||||
listeners: HashMap::new(),
|
|
||||||
|
|
||||||
provides,
|
provides,
|
||||||
depends: dedupe_names(depends),
|
depends: dedupe_names(depends),
|
||||||
@ -306,9 +301,7 @@ impl PluginManager {
|
|||||||
state: PluginState::Registered,
|
state: PluginState::Registered,
|
||||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||||
path: jar_path.as_ref().to_path_buf(),
|
path: jar_path.as_ref().to_path_buf(),
|
||||||
instance: None,
|
|
||||||
commands,
|
commands,
|
||||||
listeners: HashMap::new(),
|
|
||||||
|
|
||||||
provides,
|
provides,
|
||||||
depends: dedupe_names(depends),
|
depends: dedupe_names(depends),
|
||||||
@ -323,30 +316,43 @@ impl PluginManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
pub fn enable_all_plugins(&mut self, _env: &mut Env) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
pub fn disable_all_plugins(&mut self, _env: &mut Env) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn instantiate_all_plugins(
|
pub fn instantiate_all_plugins(
|
||||||
&mut self,
|
&mut self,
|
||||||
jvm: &Jvm,
|
env: &mut Env,
|
||||||
plugin_folder: &Path,
|
plugin_folder: &Path,
|
||||||
_server: &Arc<Context>,
|
_server: &Arc<Context>,
|
||||||
_command_tx: mpsc::Sender<JvmCommand>,
|
_command_tx: mpsc::Sender<JvmCommand>,
|
||||||
_command_manager: &mut CommandManager,
|
_command_manager: &mut CommandManager,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("Bootstrapping Java plugins from {:?}", plugin_folder);
|
tracing::info!("Bootstrapping Java plugins from {:?}", plugin_folder);
|
||||||
let _ = jvm.invoke_static(
|
let path_str = plugin_folder.to_string_lossy();
|
||||||
"org.patchbukkit.loader.PatchBukkitBootstrap",
|
let j_str = env.new_string(&path_str)?;
|
||||||
"bootstrapPlugins",
|
let result = env.call_static_method(
|
||||||
&[InvocationArg::try_from(
|
jni::jni_str!("org/patchbukkit/loader/PatchBukkitBootstrap"),
|
||||||
&plugin_folder.to_string_lossy().to_string(),
|
jni::jni_str!("bootstrapPlugins"),
|
||||||
)?],
|
jni::jni_sig!("(Ljava/lang/String;)Z"),
|
||||||
|
&[(&j_str).into()],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(val) => {
|
||||||
|
if !val.z()? {
|
||||||
|
tracing::error!("Bootstrap of Java plugins reported errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to bootstrap Java plugins: {:?}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,39 +8,49 @@ use anyhow::Result;
|
|||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
|
|
||||||
#[derive(Embed)]
|
#[derive(Embed)]
|
||||||
#[folder = "resources/"]
|
#[folder = "resources/jassets/"]
|
||||||
pub struct Resources;
|
pub struct Resources;
|
||||||
|
|
||||||
pub fn setup_j4rs(j4rs_folder: &Path) -> Result<()> {
|
pub fn setup_resources(resources_folder: &Path) -> Result<()> {
|
||||||
sync_embedded_resources(j4rs_folder)?;
|
sync_embedded_resources(resources_folder)?;
|
||||||
cleanup_stale_files(j4rs_folder);
|
cleanup_stale_files(resources_folder);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup_stale_files(j4rs_folder: &Path) {
|
fn cleanup_stale_files(resources_folder: &Path) {
|
||||||
let embedded_paths: HashSet<PathBuf> = Resources::iter()
|
let embedded_paths: HashSet<PathBuf> = Resources::iter()
|
||||||
.map(|p| PathBuf::from(p.to_string()))
|
.map(|p| PathBuf::from(p.to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for entry in walkdir::WalkDir::new(j4rs_folder)
|
for entry in walkdir::WalkDir::new(resources_folder)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.file_type().is_file())
|
.filter(|e| e.file_type().is_file())
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if let Ok(rel_path) = path.strip_prefix(j4rs_folder)
|
if let Ok(rel_path) = path.strip_prefix(resources_folder)
|
||||||
&& !embedded_paths.contains(rel_path)
|
&& !embedded_paths.contains(rel_path)
|
||||||
{
|
{
|
||||||
tracing::warn!("Removing stale embedded file: {}", rel_path.display());
|
tracing::warn!("Removing stale embedded file: {}", rel_path.display());
|
||||||
let _ = fs::remove_file(path);
|
let _ = fs::remove_file(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up any empty subdirectories in resources_folder
|
||||||
|
for entry in walkdir::WalkDir::new(resources_folder)
|
||||||
|
.contents_first(true)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().is_dir() && e.path() != resources_folder)
|
||||||
|
{
|
||||||
|
let _ = fs::remove_dir(entry.path());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync_embedded_resources(j4rs_folder: &Path) -> Result<()> {
|
pub fn sync_embedded_resources(resources_folder: &Path) -> Result<()> {
|
||||||
for resource_path_str in Resources::iter() {
|
for resource_path_str in Resources::iter() {
|
||||||
let resource_path = j4rs_folder.join(resource_path_str.to_string());
|
let resource_path = resources_folder.join(resource_path_str.to_string());
|
||||||
let resource = Resources::get(&resource_path_str).unwrap();
|
let resource = Resources::get(&resource_path_str).unwrap();
|
||||||
|
|
||||||
if resource_path.exists() {
|
if resource_path.exists() {
|
||||||
|
|||||||
141
rust/src/lib.rs
141
rust/src/lib.rs
@ -13,17 +13,13 @@ pub mod java;
|
|||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
|
||||||
use directories::setup_directories;
|
use directories::setup_directories;
|
||||||
use java::jar::discover_jar_files;
|
use tokio::sync::{mpsc, oneshot};
|
||||||
use tokio::sync::{
|
|
||||||
mpsc::{self, Receiver},
|
|
||||||
oneshot,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::patchbukkit::PatchBukkitConfig,
|
config::patchbukkit::PatchBukkitConfig,
|
||||||
java::{
|
java::{
|
||||||
jvm::{commands::JvmCommand, worker::JvmWorker},
|
jvm::{commands::JvmCommand, worker::JvmWorker},
|
||||||
resources::setup_j4rs,
|
resources::setup_resources,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,77 +36,91 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
|||||||
.map_err(|e| format!("Failed to setup PatchBukkit config: {e}"))?;
|
.map_err(|e| format!("Failed to setup PatchBukkit config: {e}"))?;
|
||||||
|
|
||||||
// Manage embedded resources
|
// Manage embedded resources
|
||||||
setup_j4rs(&dirs.j4rs).map_err(|e| format!("Failed to setup J4RS: {e}"))?;
|
setup_resources(&dirs.jassets).map_err(|e| format!("Failed to setup resources: {e}"))?;
|
||||||
|
|
||||||
{
|
let runtime_handle = plugin.runtime.handle().clone();
|
||||||
|
let command_tx = plugin.command_tx.clone();
|
||||||
|
let server_clone = server.clone();
|
||||||
|
|
||||||
|
// Run JVM initialization and Java plugin bootstrap in a background task
|
||||||
|
// on PatchBukkit's dedicated runtime so Pumpkin's main startup is never blocked.
|
||||||
|
plugin.runtime.spawn(async move {
|
||||||
|
tracing::info!("Initializing JVM in background...");
|
||||||
|
|
||||||
|
// 1. Initialize JVM
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
plugin
|
if let Err(e) = command_tx
|
||||||
.command_tx
|
|
||||||
.send(JvmCommand::Initialize {
|
.send(JvmCommand::Initialize {
|
||||||
j4rs_path: dirs.j4rs,
|
jassets_path: dirs.jassets.clone(),
|
||||||
respond_to: tx,
|
respond_to: tx,
|
||||||
context: server.clone(),
|
context: server_clone.clone(),
|
||||||
command_tx: plugin.command_tx.clone(),
|
runtime_handle,
|
||||||
|
command_tx: command_tx.clone(),
|
||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to send command to initialize J4RS: {e}"))?;
|
{
|
||||||
rx.await
|
tracing::error!("Failed to send command to initialize JVM: {e}");
|
||||||
.map_err(|e| format!("Unable to receive response from J4RS initialization: {e}"))?
|
return;
|
||||||
.map_err(|e| format!("Failed to initialize all plugins: {e}"))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
match rx.await {
|
||||||
|
Ok(Ok(())) => tracing::info!("JVM initialization completed successfully"),
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
tracing::error!("Failed to initialize JVM: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Unable to receive response from JVM initialization: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Instantiate all Java plugins
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
plugin
|
if let Err(e) = command_tx
|
||||||
.command_tx
|
|
||||||
.send(JvmCommand::InstantiateAllPlugins {
|
.send(JvmCommand::InstantiateAllPlugins {
|
||||||
plugins_dir: dirs.plugins.clone(),
|
plugins_dir: dirs.plugins.clone(),
|
||||||
respond_to: tx,
|
respond_to: tx,
|
||||||
server: server.clone(),
|
server: server_clone.clone(),
|
||||||
command_tx: plugin.command_tx.clone(),
|
command_tx: command_tx.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to send command to instantiate plugins: {e}"))?;
|
{
|
||||||
rx.await
|
tracing::error!("Failed to send command to instantiate plugins: {e}");
|
||||||
.map_err(|e| format!("Unable to receive response from instantiate plugins: {e}"))?
|
return;
|
||||||
.map_err(|e| format!("Failed to instantiate all plugins: {e}"))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
match rx.await {
|
||||||
|
Ok(Ok(())) => tracing::info!("Java plugins instantiated successfully"),
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
tracing::error!("Failed to instantiate Java plugins: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Unable to receive response from instantiate plugins: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Enable all Java plugins
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
plugin
|
if let Err(e) = command_tx
|
||||||
.command_tx
|
|
||||||
.send(JvmCommand::EnableAllPlugins { respond_to: tx })
|
.send(JvmCommand::EnableAllPlugins { respond_to: tx })
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to send command to enable all plugins: {e}"))?;
|
{
|
||||||
rx.await
|
tracing::error!("Failed to send command to enable all plugins: {e}");
|
||||||
.map_err(|e| format!("Unable to receive response from enable all plugins: {e}"))?
|
return;
|
||||||
.map_err(|e| format!("Failed to enable all plugins: {e}"))?;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Register Bukkit plugin loader with Pumpkin so Bukkit/Paper plugins appear in Pumpkin's native /plugins command
|
match rx.await {
|
||||||
let bukkit_loader = Arc::new(crate::java::plugin::loader::BukkitPluginLoader);
|
Ok(Ok(())) => tracing::info!("PatchBukkit background initialization complete"),
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
Ok(Err(e)) => tracing::error!("Failed to enable all plugins: {e}"),
|
||||||
.enable_all()
|
Err(e) => tracing::error!("Unable to receive response from enable all plugins: {e}"),
|
||||||
.build()
|
|
||||||
.map_err(|e| format!("Failed to build Tokio runtime: {e}"))?;
|
|
||||||
|
|
||||||
let server_ref = server.clone();
|
|
||||||
let jar_paths: Vec<_> = discover_jar_files(&dirs.plugins).collect();
|
|
||||||
rt.block_on(async move {
|
|
||||||
server_ref
|
|
||||||
.plugin_manager
|
|
||||||
.add_loader(&server_ref.server, bukkit_loader)
|
|
||||||
.await;
|
|
||||||
for jar_path in &jar_paths {
|
|
||||||
let _ = server_ref
|
|
||||||
.plugin_manager
|
|
||||||
.try_load_plugin(&server_ref.server, jar_path)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tracing::info!("PatchBukkit loaded successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,25 +165,36 @@ async fn on_unload(&self, server: Arc<Context>) -> Result<(), String> {
|
|||||||
#[plugin_impl]
|
#[plugin_impl]
|
||||||
pub struct PatchBukkitPlugin {
|
pub struct PatchBukkitPlugin {
|
||||||
pub command_tx: mpsc::Sender<JvmCommand>,
|
pub command_tx: mpsc::Sender<JvmCommand>,
|
||||||
|
pub runtime: Arc<tokio::runtime::Runtime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PatchBukkitPlugin {
|
impl PatchBukkitPlugin {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (tx, rx) = mpsc::channel(100);
|
let runtime = Arc::new(
|
||||||
|
tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(4)
|
||||||
|
.thread_name("patchbukkit-worker")
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create PatchBukkit Tokio runtime"),
|
||||||
|
);
|
||||||
|
|
||||||
#[tokio::main]
|
let (tx, rx) = mpsc::channel(4096);
|
||||||
pub async fn jvm_thread_task(rx: Receiver<JvmCommand>) {
|
|
||||||
JvmWorker::new(rx).attach_thread().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let runtime_clone = runtime.clone();
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("patchbukkit-jvm-worker".to_string())
|
.name("patchbukkit-jvm-worker".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
jvm_thread_task(rx);
|
runtime_clone.block_on(async move {
|
||||||
|
JvmWorker::new(rx).attach_thread().await;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Self { command_tx: tx }
|
Self {
|
||||||
|
command_tx: tx,
|
||||||
|
runtime,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user