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/jassets/*
|
||||
!rust/resources/jassets/j4rs-*
|
||||
|
||||
rust/resources/deps/*
|
||||
|
||||
# Gradle cache
|
||||
.gradle/
|
||||
|
||||
@ -47,9 +47,8 @@ repositories {
|
||||
}
|
||||
|
||||
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("io.github.astonbitecode:j4rs:0.25.2")
|
||||
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-connector-basic:1.9.18")
|
||||
|
||||
@ -42,6 +42,8 @@ public class SimpleCommandMap implements CommandMap {
|
||||
knownCommands.put(label, command);
|
||||
registeredDirectly = true;
|
||||
}
|
||||
|
||||
command.register(this);
|
||||
knownCommands.put(fallbackPrefix + ":" + label, command);
|
||||
|
||||
if (command.getAliases() != null) {
|
||||
|
||||
@ -72,7 +72,7 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
JavaPlugin plugin,
|
||||
BooleanSupplier registrationCheck
|
||||
) {
|
||||
return null;
|
||||
return new org.patchbukkit.events.PatchBukkitLifecycleEventManager(plugin, registrationCheck);
|
||||
}
|
||||
|
||||
public String getStatisticCriteriaKey(Statistic statistic) {
|
||||
|
||||
@ -8,6 +8,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.EventExecutor;
|
||||
@ -24,6 +26,8 @@ import org.patchbukkit.PatchBukkitPluginManager;
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger("PatchBukkitPluginLoader");
|
||||
|
||||
public static Plugin createPlugin(
|
||||
String jarPath,
|
||||
String mainClass,
|
||||
@ -33,7 +37,7 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
try {
|
||||
File jarFile = new File(jarPath);
|
||||
if (!jarFile.exists()) {
|
||||
System.err.println(
|
||||
LOGGER.severe(
|
||||
"[PatchBukkit] Plugin file does not exist: " + jarPath
|
||||
);
|
||||
return null;
|
||||
@ -87,16 +91,17 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
pm.registerPlugin(plugin);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
PatchBukkitBootstrap.registerPluginCommands(plugin, classLoader.getDescription());
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error during onLoad() for " + mainClass + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + mainClass, t);
|
||||
}
|
||||
return plugin;
|
||||
} catch (Throwable e) {
|
||||
System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to instantiate plugin class " + mainClass, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -110,11 +115,11 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to enable plugin " + pluginName + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to enable plugin " + pluginName, t);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean disablePlugin(String pluginName) {
|
||||
@ -126,11 +131,11 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to disable plugin " + pluginName + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to disable plugin " + pluginName, t);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -107,11 +107,13 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
Class<? extends Plugin> pluginClass = jarClass.asSubclass(Plugin.class);
|
||||
Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
|
||||
loader.init((org.bukkit.plugin.java.JavaPlugin) plugin);
|
||||
try {
|
||||
org.patchbukkit.loader.PatchBukkitBootstrap.registerPluginCommands(plugin, description);
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
server.getLogger().severe("Error loading " + plugin.getName() + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
server.getLogger().log(Level.SEVERE, "Error loading " + plugin.getName(), t);
|
||||
}
|
||||
registerPlugin(plugin);
|
||||
return plugin;
|
||||
@ -145,7 +147,7 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
result.add(plugin);
|
||||
}
|
||||
} 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 java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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");
|
||||
|
||||
static {
|
||||
@ -201,20 +211,136 @@ public class PatchBukkitServer implements Server {
|
||||
root.addHandler(new java.util.logging.Handler() {
|
||||
@Override
|
||||
public void publish(java.util.logging.LogRecord record) {
|
||||
if (record == null) return;
|
||||
if (IN_LOGGING.get()) return;
|
||||
IN_LOGGING.set(true);
|
||||
try {
|
||||
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
||||
NativeBridgeFfi.sendLog(
|
||||
SendLogRequest.newBuilder()
|
||||
.setLevel(logLevel)
|
||||
.setMessage(record.getMessage() != null ? record.getMessage() : "")
|
||||
.setLoggerName(record.getLoggerName() != null ? record.getLoggerName() : "")
|
||||
.build()
|
||||
);
|
||||
} catch (Throwable ignored) {}
|
||||
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(
|
||||
SendLogRequest.newBuilder()
|
||||
.setLevel(logLevel)
|
||||
.setMessage(message)
|
||||
.setLoggerName(loggerName)
|
||||
.build()
|
||||
);
|
||||
} 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 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);
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.pluginManager.getEventManager();
|
||||
}
|
||||
@ -312,10 +453,11 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public @NotNull String getMinecraftVersion() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getMinecraftVersion'"
|
||||
);
|
||||
String version = this.bukkitVersion;
|
||||
if (version != null && version.contains("-")) {
|
||||
return version.split("-")[0];
|
||||
}
|
||||
return version != null && !version.equals("Unknown-Version") ? version : "1.21.4";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -325,56 +467,36 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public int getMaxPlayers() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getMaxPlayers'"
|
||||
);
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxPlayers(int maxPlayers) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'setMaxPlayers'"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getPort'"
|
||||
);
|
||||
return 25565;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewDistance() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getViewDistance'"
|
||||
);
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSimulationDistance() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getSimulationDistance'"
|
||||
);
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIp() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getIp'");
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getWorldType() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getWorldType'"
|
||||
);
|
||||
return "DEFAULT";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -79,6 +79,8 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
||||
registeredDirectly = true;
|
||||
}
|
||||
|
||||
command.register(this);
|
||||
|
||||
registerVariants(label, command);
|
||||
registerVariants(fallbackPrefix + ":" + label, command);
|
||||
if (!clean.isEmpty()) {
|
||||
@ -105,7 +107,8 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
||||
.setPluginName(fallbackPrefix)
|
||||
.build();
|
||||
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;
|
||||
@ -140,12 +143,66 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
||||
|
||||
try {
|
||||
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) {
|
||||
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
|
||||
public void clearCommands() {
|
||||
knownCommands.clear();
|
||||
|
||||
@ -1240,15 +1240,18 @@ public class PatchBukkitPlayer
|
||||
|
||||
@Override
|
||||
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
|
||||
public void setAllowFlight(boolean flight) {
|
||||
var abilities = NativeBridgeFfi.getAbilities(BridgeUtils.convertUuid(this.uuid)).toBuilder();
|
||||
abilities.setAllowFlying(flight);
|
||||
if (abilities.getFlying()) abilities.setFlying(false);
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(BridgeUtils.convertUuid(this.uuid)).build());
|
||||
var playerUuid = BridgeUtils.convertUuid(this.uuid);
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||
builder.setAllowFlying(flight);
|
||||
if (builder.getFlying()) builder.setFlying(false);
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1335,14 +1338,17 @@ public class PatchBukkitPlayer
|
||||
|
||||
@Override
|
||||
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
|
||||
public void setFlying(boolean value) {
|
||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setFlying(value).build();
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||
builder.setFlying(value);
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1351,8 +1357,10 @@ public class PatchBukkitPlayer
|
||||
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
||||
}
|
||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setFlySpeed(value).build();
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||
builder.setFlySpeed(value);
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1361,18 +1369,22 @@ public class PatchBukkitPlayer
|
||||
throw new IllegalArgumentException("Speed value " + value + " is not between -1.0 and 1.0");
|
||||
}
|
||||
var playerUuid = BridgeUtils.convertUuid(this.getUniqueId());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid).toBuilder().setWalkSpeed(value).build();
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(abilities).setUuid(playerUuid).build());
|
||||
var abilities = NativeBridgeFfi.getAbilities(playerUuid);
|
||||
var builder = (abilities != null ? abilities.toBuilder() : patchbukkit.abilities.Abilities.newBuilder());
|
||||
builder.setWalkSpeed(value);
|
||||
NativeBridgeFfi.setAbilities(SetAbilitiesRequest.newBuilder().setAbilities(builder.build()).setUuid(playerUuid).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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
|
||||
|
||||
@ -33,6 +33,7 @@ import patchbukkit.events.BlockIgniteEvent;
|
||||
import patchbukkit.events.ServerCommandEvent;
|
||||
import patchbukkit.events.SignChangeEvent;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class PatchBukkitEventFactory {
|
||||
@ -44,11 +45,22 @@ public class PatchBukkitEventFactory {
|
||||
Event event = Event.parseFrom(data);
|
||||
return createEvent(event);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
LOGGER.severe("Failed to parse Event: " + e.getMessage());
|
||||
LOGGER.log(Level.SEVERE, "Failed to parse Event", e);
|
||||
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
|
||||
public static org.bukkit.event.Event createEvent(@NotNull Event event) {
|
||||
try {
|
||||
@ -427,7 +439,10 @@ public class PatchBukkitEventFactory {
|
||||
java.util.UUID uuid = java.util.UUID.fromString(uuidStr);
|
||||
Player player = Bukkit.getServer().getPlayer(uuid);
|
||||
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;
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
@ -53,6 +53,8 @@ public class PatchBukkitEventManager {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
private final Set<String> registeredBridgeEvents = java.util.concurrent.ConcurrentHashMap.newKeySet();
|
||||
|
||||
public void callEvent(@NotNull Event event) throws IllegalStateException {
|
||||
if (event.isAsynchronous() && this.server.isPrimaryThread()) {
|
||||
throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
|
||||
@ -60,285 +62,7 @@ public class PatchBukkitEventManager {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -418,9 +142,19 @@ public class PatchBukkitEventManager {
|
||||
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
|
||||
this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
|
||||
|
||||
for (RegisteredListener rl : entry.getValue()) {
|
||||
int priorityOrdinal = Math.min(rl.getPriority().ordinal(), 4);
|
||||
var request = RegisterEventRequest.newBuilder().setEventType(entry.getKey().getName()).setPluginName(plugin.getName()).setPriority(priorityOrdinal).setBlocking(true).build();
|
||||
String eventName = entry.getKey().getName();
|
||||
String key = plugin.getName() + ":" + eventName;
|
||||
if (registeredBridgeEvents.add(key)) {
|
||||
int maxPriority = 0;
|
||||
for (RegisteredListener rl : entry.getValue()) {
|
||||
maxPriority = Math.max(maxPriority, Math.min(rl.getPriority().ordinal(), 4));
|
||||
}
|
||||
var request = RegisterEventRequest.newBuilder()
|
||||
.setEventType(eventName)
|
||||
.setPluginName(plugin.getName())
|
||||
.setPriority(maxPriority)
|
||||
.setBlocking(true)
|
||||
.build();
|
||||
NativeBridgeFfi.registerEvent(request);
|
||||
}
|
||||
}
|
||||
@ -438,10 +172,17 @@ public class PatchBukkitEventManager {
|
||||
executor = new TimedEventExecutor(executor, plugin, null, event);
|
||||
this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
||||
|
||||
int priorityOrdinal = Math.min(priority.ordinal(), 4);
|
||||
var request = RegisterEventRequest.newBuilder().setEventType(event.getName()).setPluginName(plugin.getName()).setPriority(priorityOrdinal).setBlocking(true).build();
|
||||
NativeBridgeFfi.registerEvent(request);
|
||||
|
||||
String key = plugin.getName() + ":" + event.getName();
|
||||
if (registeredBridgeEvents.add(key)) {
|
||||
int priorityOrdinal = Math.min(priority.ordinal(), 4);
|
||||
var request = RegisterEventRequest.newBuilder()
|
||||
.setEventType(event.getName())
|
||||
.setPluginName(plugin.getName())
|
||||
.setPriority(priorityOrdinal)
|
||||
.setBlocking(true)
|
||||
.build();
|
||||
NativeBridgeFfi.registerEvent(request);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -534,5 +275,6 @@ public class PatchBukkitEventManager {
|
||||
|
||||
public void clearEvents() {
|
||||
HandlerList.unregisterAll();
|
||||
registeredBridgeEvents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@ -17,11 +19,13 @@ import org.patchbukkit.command.PatchBukkitCommandMap;
|
||||
|
||||
public class PatchBukkitBootstrap {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger("PatchBukkitBootstrap");
|
||||
|
||||
public static boolean bootstrapPlugins(String pluginsDirPath) {
|
||||
try {
|
||||
File pluginsDir = new File(pluginsDirPath);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -29,7 +33,7 @@ public class PatchBukkitBootstrap {
|
||||
findJarsRecursive(pluginsDir, jarFiles);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -42,8 +46,7 @@ public class PatchBukkitBootstrap {
|
||||
holders.put(holder.name.toLowerCase(Locale.ENGLISH), holder);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to parse plugin JAR " + jarFile.getName() + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to parse plugin JAR " + jarFile.getName(), t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,8 +56,7 @@ public class PatchBukkitBootstrap {
|
||||
try {
|
||||
loadAndRegisterPlugin(holder, holders);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to load plugin " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to load plugin " + holder.name, t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +66,9 @@ public class PatchBukkitBootstrap {
|
||||
if (holder.pluginInstance != null) {
|
||||
try {
|
||||
pm.enablePlugin(holder.pluginInstance);
|
||||
System.out.println("[PatchBukkit] Enabled plugin: " + holder.name);
|
||||
LOGGER.info("[PatchBukkit] Enabled plugin: " + holder.name);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error enabling plugin " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error enabling plugin " + holder.name, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,8 +76,7 @@ public class PatchBukkitBootstrap {
|
||||
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Fatal error during bootstrap: " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Fatal error during bootstrap", t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -210,14 +210,17 @@ public class PatchBukkitBootstrap {
|
||||
try {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error during onLoad() for " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + holder.name, t);
|
||||
}
|
||||
|
||||
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) {
|
||||
Map<String, Map<String, Object>> commands = holder.description.getCommands();
|
||||
Map<String, Map<String, Object>> commands = description.getCommands();
|
||||
if (commands != null) {
|
||||
for (Map.Entry<String, Map<String, Object>> entry : commands.entrySet()) {
|
||||
String cmdName = entry.getKey();
|
||||
@ -247,8 +250,8 @@ public class PatchBukkitBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
commandMap.register(cmdName, holder.name, pluginCmd);
|
||||
System.out.println("[PatchBukkit] Registered command: /" + cmdName + " for plugin " + holder.name);
|
||||
commandMap.register(cmdName, description.getName(), pluginCmd);
|
||||
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 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) {
|
||||
this(null, null, null, registryKey);
|
||||
@ -51,19 +54,49 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
RegistryKey<B> registryKey
|
||||
) {
|
||||
this.registryKey = registryKey;
|
||||
if (registryType != null || extractor != null || factory != null) {
|
||||
initialize(registryType, extractor, factory);
|
||||
this.registryType = registryType;
|
||||
this.extractor = extractor;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public void ensureInitialized() {
|
||||
if (initialized) return;
|
||||
synchronized (this) {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
initializeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void initialize(
|
||||
RegistryType registryType,
|
||||
Function<GetRegistryDataResponse, List<P>> extractor,
|
||||
Function<P, B> factory
|
||||
) {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
private void initializeInternal() {
|
||||
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 != 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
|
||||
if (registryKey != null) {
|
||||
@ -89,6 +122,8 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
}
|
||||
} 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 {
|
||||
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
||||
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")
|
||||
@ -162,6 +169,7 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
ensureInitialized();
|
||||
B value = entries.get(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
@ -228,11 +236,13 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
|
||||
@Override
|
||||
public boolean hasTag(TagKey<B> key) {
|
||||
ensureInitialized();
|
||||
return key != null && tags.containsKey(key.key().asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Tag<B> getTag(TagKey<B> key) {
|
||||
ensureInitialized();
|
||||
PatchBukkitTag<B> tag = tags.get(key.key().asString());
|
||||
if (tag == null) {
|
||||
throw new NoSuchElementException("Unknown tag: " + key.key().asString());
|
||||
@ -246,25 +256,30 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<Tag<B>> getTags() {
|
||||
ensureInitialized();
|
||||
return Collections.unmodifiableCollection(tags.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Stream<B> stream() {
|
||||
ensureInitialized();
|
||||
return entries.values().stream();
|
||||
}
|
||||
|
||||
public @NonNull Stream<NamespacedKey> keyStream() {
|
||||
ensureInitialized();
|
||||
return entries.keySet().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
ensureInitialized();
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Iterator<B> iterator() {
|
||||
ensureInitialized();
|
||||
return Collections.unmodifiableCollection(entries.values()).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,24 +53,16 @@ public class PatchBukkitRegistryAccess extends io.papermc.paper.registry.PaperRe
|
||||
return (Registry<T>) existing;
|
||||
}
|
||||
|
||||
PatchBukkitRegistry reg = new PatchBukkitRegistry(registryKey);
|
||||
PatchBukkitRegistry reg;
|
||||
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(registryKey);
|
||||
if (factoryEntry != null) {
|
||||
reg = new PatchBukkitRegistry(factoryEntry.registryType(), factoryEntry.extractor(), factoryEntry.factory(), registryKey);
|
||||
} else {
|
||||
reg = new PatchBukkitRegistry(registryKey);
|
||||
}
|
||||
|
||||
Registry<?> winner = instances.putIfAbsent(registryKey, reg);
|
||||
if (winner != null) {
|
||||
return (Registry<T>) winner;
|
||||
}
|
||||
|
||||
try {
|
||||
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(registryKey);
|
||||
if (factoryEntry != null) {
|
||||
reg.initialize(factoryEntry.registryType(), factoryEntry.extractor(), factoryEntry.factory());
|
||||
} else {
|
||||
reg.initialize(null, null, null);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Ignore failure during registry initialization safely
|
||||
}
|
||||
|
||||
return (Registry<T>) reg;
|
||||
return (Registry<T>) (winner != null ? winner : reg);
|
||||
}
|
||||
|
||||
@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
|
||||
public void testInspectBlockTypeAndDataMethods() {
|
||||
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"
|
||||
tracing = "0.1"
|
||||
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"] }
|
||||
walkdir = "2"
|
||||
zip = "8.6"
|
||||
@ -39,7 +39,6 @@ prost = "0.14"
|
||||
toml = "1.1"
|
||||
|
||||
[build-dependencies]
|
||||
j4rs = { version = "0.25.2", artifact = "cdylib", lib = true }
|
||||
env_logger = "0.11.11"
|
||||
log = "0.4"
|
||||
prost-build = "0.14"
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use j4rs::{JvmBuilder, MavenArtifactRepo, MavenSettings};
|
||||
|
||||
pub fn setup_java(base: PathBuf) {
|
||||
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();
|
||||
let java_path = java_path.join("java");
|
||||
|
||||
@ -16,72 +14,15 @@ pub fn setup_java(base: PathBuf) {
|
||||
.join("libs")
|
||||
.join("patchbukkit.jar");
|
||||
|
||||
let jassets = resources.join("jassets");
|
||||
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() {
|
||||
if !patchbukkit_jar.exists() {
|
||||
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();
|
||||
|
||||
let cdylib = std::env::var("CARGO_CDYLIB_FILE_J4RS").unwrap();
|
||||
let cdylib = PathBuf::from(cdylib);
|
||||
|
||||
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:?}"))
|
||||
let dest_patchbukkit = jassets.join("patchbukkit.jar");
|
||||
fs::copy(&patchbukkit_jar, &dest_patchbukkit)
|
||||
.map_err(|err| format!("Failed to copy patchbukkit.jar to resources/jassets: {err:?}"))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -106,7 +106,6 @@ pub unsafe extern "C" fn {fn_name}(
|
||||
output_len: *mut usize,
|
||||
) -> *mut u8 {{
|
||||
use prost::Message;
|
||||
tracing::debug!("{fn_name}: called with input_ptr={{:?}}, input_len={{}}", input_ptr, input_len);
|
||||
if input_ptr.is_null() {{
|
||||
tracing::warn!("{fn_name}: null input (ptr={{:?}}, len={{}})", input_ptr, input_len);
|
||||
unsafe {{ *output_len = 0 }};
|
||||
@ -123,16 +122,13 @@ pub unsafe extern "C" fn {fn_name}(
|
||||
return std::ptr::null_mut();
|
||||
}};
|
||||
let Some(response) = {0}::{fn_name}_impl(request) else {{
|
||||
tracing::debug!("{fn_name}: impl returned None");
|
||||
unsafe {{ *output_len = 0 }};
|
||||
return std::ptr::null_mut();
|
||||
}};
|
||||
let encoded = response.encode_to_vec().into_boxed_slice();
|
||||
let len = encoded.len();
|
||||
unsafe {{ *output_len = len }};
|
||||
let ptr = Box::into_raw(encoded) as *mut u8;
|
||||
tracing::debug!("{fn_name}: returning ptr={{:?}}, len={{}}", ptr, len);
|
||||
ptr
|
||||
Box::into_raw(encoded) as *mut u8
|
||||
}}
|
||||
"#,
|
||||
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<()> {{
|
||||
use j4rs::InvocationArg;"#
|
||||
pub fn initialize_ffi_callbacks(env: &mut jni::Env) -> anyhow::Result<()> {{"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for service in &state.services {
|
||||
let class_slash = service.java_class.replace('.', "/");
|
||||
let sig = format!("({})V", "J".repeat(service.methods.len()));
|
||||
|
||||
writeln!(
|
||||
file,
|
||||
r#"
|
||||
// Initialize {}
|
||||
if let Err(e) = jvm.invoke_static(
|
||||
"{}",
|
||||
"init",
|
||||
&["#,
|
||||
service.java_class, service.java_class
|
||||
// Initialize {class_slash}
|
||||
{{
|
||||
let args = ["#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for method in &service.methods {
|
||||
writeln!(file,
|
||||
" InvocationArg::try_from(crate::proto::{}::{} as *const () as i64)?.into_primitive()?,",
|
||||
writeln!(
|
||||
file,
|
||||
" jni::objects::JValue::Long(crate::proto::{}::{} as *const () as i64),",
|
||||
service.module_path, method.fn_name
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
writeln!(
|
||||
file,
|
||||
r#" ],
|
||||
) {{
|
||||
tracing::error!("Failed to initialize FFI service: {{:?}}", e);
|
||||
}}
|
||||
r#" ];
|
||||
if let Err(e) = env.call_static_method(
|
||||
jni::jni_str!("{class_slash}"),
|
||||
jni::jni_str!("init"),
|
||||
jni::jni_sig!("{sig}"),
|
||||
&args,
|
||||
) {{
|
||||
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(
|
||||
"{}",
|
||||
"initFree",
|
||||
&[InvocationArg::try_from(ffi_free_bytes as *const () as i64)?.into_primitive()?],
|
||||
) {{
|
||||
tracing::error!("Failed to initialize FFI service initFree: {{:?}}", e);
|
||||
if let Err(e) = env.call_static_method(
|
||||
jni::jni_str!("{class_slash}"),
|
||||
jni::jni_str!("initFree"),
|
||||
jni::jni_sig!("(J)V"),
|
||||
&[jni::objects::JValue::Long(ffi_free_bytes as *const () as i64)],
|
||||
) {{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@ -25,8 +25,8 @@ pub struct JavaCommandExecutor {
|
||||
#[derive(Clone)]
|
||||
pub enum SimpleCommandSender {
|
||||
Console,
|
||||
/// UUID, Name
|
||||
Player(String, String),
|
||||
/// UUID, Name, is_op
|
||||
Player(String, String, bool),
|
||||
}
|
||||
|
||||
pub struct AnyCommandNode {
|
||||
@ -94,7 +94,8 @@ impl ArgumentConsumer for AnyCommandNode {
|
||||
|
||||
Box::pin(async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.command_tx
|
||||
if let Err(e) = self
|
||||
.command_tx
|
||||
.send(JvmCommand::GetCommandTabComplete {
|
||||
command_sender,
|
||||
full_command: input.to_string(),
|
||||
@ -102,9 +103,15 @@ impl ArgumentConsumer for AnyCommandNode {
|
||||
location,
|
||||
})
|
||||
.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 {
|
||||
match val {
|
||||
CommandSender::Console | CommandSender::Rcon(_) | CommandSender::Dummy => Self::Console,
|
||||
CommandSender::Player(player) => Self::Player(
|
||||
player.gameprofile.id.to_string(),
|
||||
player.gameprofile.name.clone(),
|
||||
),
|
||||
CommandSender::Player(player) => {
|
||||
let is_op =
|
||||
player.permission_lvl.load() >= pumpkin_util::permission::PermissionLvl::Two;
|
||||
Self::Player(
|
||||
player.gameprofile.id.to_string(),
|
||||
player.gameprofile.name.clone(),
|
||||
is_op,
|
||||
)
|
||||
}
|
||||
CommandSender::CommandBlock(_block_entity, _world) => Self::Console,
|
||||
}
|
||||
}
|
||||
@ -129,6 +141,10 @@ impl CommandExecutor for JavaCommandExecutor {
|
||||
_server: &'a pumpkin::server::Server,
|
||||
args: &'a pumpkin::command::args::ConsumedArgs<'a>,
|
||||
) -> pumpkin::command::CommandResult<'a> {
|
||||
if let CommandSender::Player(player) = sender {
|
||||
crate::java::native_callbacks::utils::cache_player(player.clone());
|
||||
}
|
||||
|
||||
Box::pin(async move {
|
||||
let full_command = match args.get(ARG_ANY) {
|
||||
Some(Arg::Msg(msg)) => {
|
||||
@ -147,15 +163,21 @@ impl CommandExecutor for JavaCommandExecutor {
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, _rx) = oneshot::channel();
|
||||
self.command_tx
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if let Err(e) = self
|
||||
.command_tx
|
||||
.send(JvmCommand::TriggerCommand {
|
||||
full_command,
|
||||
respond_to: tx,
|
||||
command_sender: sender.into(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
tracing::error!("Failed to send command to JVM worker: {e}");
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let _ = rx.await;
|
||||
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 plugins: PathBuf,
|
||||
pub plugin_updates: PathBuf,
|
||||
pub j4rs: PathBuf,
|
||||
pub jassets: PathBuf,
|
||||
}
|
||||
|
||||
@ -20,8 +19,7 @@ pub fn setup_directories(server: &Context) -> Result<PatchBukkitDirectories, Str
|
||||
|
||||
let plugins = base.join("patchbukkit-plugins");
|
||||
let plugin_updates = plugins.join("update");
|
||||
let j4rs = base.join("j4rs");
|
||||
let jassets = j4rs.join("jassets");
|
||||
let jassets = base.join("jassets");
|
||||
|
||||
fs::create_dir_all(&jassets)
|
||||
.map_err(|err| format!("Failed to create jassets folder: {err:?}"))?;
|
||||
@ -58,7 +56,6 @@ pub fn setup_directories(server: &Context) -> Result<PatchBukkitDirectories, Str
|
||||
base,
|
||||
plugins,
|
||||
plugin_updates,
|
||||
j4rs,
|
||||
jassets,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin::entity::player::Player;
|
||||
use pumpkin::plugin::{BoxFuture, Cancellable, EventHandler, Payload};
|
||||
use pumpkin::server::Server;
|
||||
use pumpkin_api_macros::with_runtime;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
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<()> {
|
||||
if let Data::PlayerJoin(event) = data {
|
||||
self.join_message = serde_json::from_str(&event.join_message).ok()?;
|
||||
server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?;
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::PlayerJoin(event) = data
|
||||
&& let Ok(msg) = serde_json::from_str(&event.join_message)
|
||||
{
|
||||
self.join_message = msg;
|
||||
}
|
||||
|
||||
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<()> {
|
||||
if let Data::PlayerQuit(event) = data {
|
||||
self.leave_message = serde_json::from_str(&event.quit_message).ok()?;
|
||||
server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?;
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::PlayerQuit(event) = data
|
||||
&& let Ok(msg) = serde_json::from_str(&event.quit_message)
|
||||
{
|
||||
self.leave_message = msg;
|
||||
}
|
||||
|
||||
Some(())
|
||||
@ -603,23 +603,51 @@ impl<E: PatchBukkitEvent> PatchBukkitEventHandler<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[with_runtime(global)]
|
||||
impl<E> EventHandler<E> for PatchBukkitEventHandler<E>
|
||||
where
|
||||
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>(
|
||||
&'a self,
|
||||
server: &'a Arc<Server>,
|
||||
event: &'a mut 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: event.to_payload(server.clone()),
|
||||
payload,
|
||||
respond_to: tx,
|
||||
plugin: self.plugin_name.clone(),
|
||||
})
|
||||
@ -632,7 +660,9 @@ where
|
||||
match rx.await {
|
||||
Ok(response) => {
|
||||
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(_) => {
|
||||
tracing::warn!("JVM worker dropped response channel for event");
|
||||
|
||||
@ -22,9 +22,10 @@ pub enum LoadPluginResult {
|
||||
|
||||
pub enum JvmCommand {
|
||||
Initialize {
|
||||
j4rs_path: PathBuf,
|
||||
jassets_path: PathBuf,
|
||||
respond_to: oneshot::Sender<Result<()>>,
|
||||
context: Arc<Context>,
|
||||
runtime_handle: tokio::runtime::Handle,
|
||||
command_tx: mpsc::Sender<Self>,
|
||||
config: PatchBukkitConfig,
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use j4rs::{InvocationArg, Jvm, JvmBuilder};
|
||||
use jni::{Env, InitArgsBuilder, JNIVersion, JavaVM};
|
||||
use pumpkin::plugin::Context;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@ -21,7 +21,7 @@ pub struct JvmWorker {
|
||||
pub plugin_manager: PluginManager,
|
||||
pub event_manager: EventManager,
|
||||
pub command_manager: CommandManager,
|
||||
jvm: Option<j4rs::Jvm>,
|
||||
jvm: Option<Arc<JavaVM>>,
|
||||
context: Option<Arc<Context>>,
|
||||
}
|
||||
|
||||
@ -44,21 +44,22 @@ impl JvmWorker {
|
||||
while let Some(command) = self.command_rx.recv().await {
|
||||
match command {
|
||||
JvmCommand::Initialize {
|
||||
j4rs_path,
|
||||
jassets_path,
|
||||
respond_to,
|
||||
context,
|
||||
runtime_handle,
|
||||
command_tx,
|
||||
config,
|
||||
} => {
|
||||
init_callback_context(
|
||||
context.clone(),
|
||||
tokio::runtime::Handle::current(),
|
||||
runtime_handle,
|
||||
command_tx.clone(),
|
||||
config,
|
||||
)
|
||||
.unwrap();
|
||||
self.context = Some(context);
|
||||
let result = self.initialize_jvm(&j4rs_path);
|
||||
let result = self.initialize_jvm(&jassets_path);
|
||||
let _ = respond_to.send(result);
|
||||
}
|
||||
JvmCommand::LoadPlugin {
|
||||
@ -105,38 +106,49 @@ impl JvmWorker {
|
||||
server,
|
||||
command_tx,
|
||||
} => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
};
|
||||
|
||||
let _ = respond_to.send(
|
||||
self.plugin_manager
|
||||
.instantiate_all_plugins(
|
||||
jvm,
|
||||
let res = if let Some(ref jvm) = self.jvm {
|
||||
let plugins_dir = plugins_dir.clone();
|
||||
let server = server.clone();
|
||||
let command_tx = command_tx.clone();
|
||||
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||
self.plugin_manager.instantiate_all_plugins(
|
||||
env,
|
||||
&plugins_dir,
|
||||
&server,
|
||||
command_tx,
|
||||
&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 } => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
let res = if let Some(ref jvm) = self.jvm {
|
||||
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||
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 } => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
let res = if let Some(ref jvm) = self.jvm {
|
||||
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||
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 } => {
|
||||
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
||||
@ -147,40 +159,47 @@ impl JvmWorker {
|
||||
plugin,
|
||||
payload,
|
||||
} => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
};
|
||||
|
||||
let original_event = payload.event.clone();
|
||||
let cancelled = match self.event_manager.fire_event(jvm, payload, plugin) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fire event: {e}");
|
||||
FireEventResponse {
|
||||
cancelled: false,
|
||||
data: Some(original_event),
|
||||
let response = if let Some(ref jvm) = self.jvm {
|
||||
match jvm.attach_current_thread(
|
||||
|env| -> anyhow::Result<FireEventResponse> {
|
||||
self.event_manager.fire_event(env, payload, plugin)
|
||||
},
|
||||
) {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fire event: {e}");
|
||||
FireEventResponse {
|
||||
cancelled: false,
|
||||
data: Some(original_event),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FireEventResponse {
|
||||
cancelled: false,
|
||||
data: Some(original_event),
|
||||
}
|
||||
};
|
||||
|
||||
let _ = respond_to.send(cancelled);
|
||||
let _ = respond_to.send(response);
|
||||
}
|
||||
JvmCommand::TriggerCommand {
|
||||
full_command,
|
||||
command_sender,
|
||||
respond_to,
|
||||
} => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
let res = if let Some(ref jvm) = self.jvm {
|
||||
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||
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 =
|
||||
self.command_manager
|
||||
.trigger_command(jvm, full_command, command_sender);
|
||||
|
||||
let _ = respond_to.send(result);
|
||||
let _ = respond_to.send(res);
|
||||
}
|
||||
JvmCommand::GetCommandTabComplete {
|
||||
command_sender,
|
||||
@ -188,19 +207,26 @@ impl JvmWorker {
|
||||
respond_to,
|
||||
location,
|
||||
} => {
|
||||
let jvm = match self.jvm {
|
||||
Some(ref jvm) => jvm,
|
||||
None => &Jvm::attach_thread().unwrap(),
|
||||
let res = if let Some(ref jvm) = self.jvm {
|
||||
match jvm.attach_current_thread(|env| -> anyhow::Result<_> {
|
||||
Ok(self.command_manager.get_tab_complete(
|
||||
env,
|
||||
command_sender,
|
||||
full_command,
|
||||
location,
|
||||
))
|
||||
}) {
|
||||
Ok(inner_res) => inner_res,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to get tab complete: {e}");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
let result = self.command_manager.get_tab_complete(
|
||||
jvm,
|
||||
command_sender,
|
||||
full_command,
|
||||
location,
|
||||
);
|
||||
|
||||
let _ = respond_to.send(result);
|
||||
let _ = respond_to.send(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,41 +234,85 @@ impl JvmWorker {
|
||||
tracing::info!("JVM worker thread exited");
|
||||
}
|
||||
|
||||
fn initialize_jvm(&mut self, j4rs_path: &PathBuf) -> anyhow::Result<()> {
|
||||
tracing::info!("Initializing JVM with path: {j4rs_path:?}");
|
||||
fn initialize_jvm(&mut self, jassets_path: &PathBuf) -> anyhow::Result<()> {
|
||||
tracing::info!("Initializing JVM with assets path: {jassets_path:?}");
|
||||
|
||||
let jassets = j4rs_path.join("jassets");
|
||||
let mut classpath_entries = Vec::new();
|
||||
let mut jar_paths = Vec::new();
|
||||
|
||||
if let Ok(entries) = std::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.ends_with(".jar")
|
||||
{
|
||||
classpath_entries.push(j4rs::ClasspathEntry::new(&path));
|
||||
}
|
||||
for entry in walkdir::WalkDir::new(jassets_path)
|
||||
.into_iter()
|
||||
.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())
|
||||
&& filename.ends_with(".jar")
|
||||
{
|
||||
jar_paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
let mut jvm_builder = JvmBuilder::new();
|
||||
let mut jvm_builder = jvm_builder.with_base_path(j4rs_path).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);
|
||||
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 jvm = jvm_builder.build()?;
|
||||
let has_patchbukkit = jar_paths.iter().any(|p| {
|
||||
p.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.is_some_and(|n| n.contains("patchbukkit"))
|
||||
});
|
||||
if !has_patchbukkit {
|
||||
tracing::warn!(
|
||||
"patchbukkit.jar was not found in {:?}. Server classes may fail to load!",
|
||||
jassets_path
|
||||
);
|
||||
}
|
||||
|
||||
initialize_callbacks(&jvm)?;
|
||||
let separator = if cfg!(windows) { ";" } else { ":" };
|
||||
let classpath = jar_paths
|
||||
.iter()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator);
|
||||
|
||||
setup_patchbukkit_server(&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:?}"))?;
|
||||
|
||||
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);
|
||||
|
||||
@ -251,12 +321,17 @@ impl JvmWorker {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_patchbukkit_server(jvm: &Jvm) -> anyhow::Result<()> {
|
||||
jvm.invoke_static(
|
||||
"org.patchbukkit.PatchBukkitServer",
|
||||
"initServer",
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
pub fn setup_patchbukkit_server(env: &mut Env) -> anyhow::Result<()> {
|
||||
if let Err(e) = env.call_static_method(
|
||||
jni::jni_str!("org/patchbukkit/PatchBukkitServer"),
|
||||
jni::jni_str!("initServer"),
|
||||
jni::jni_sig!("()Lorg/patchbukkit/PatchBukkitServer;"),
|
||||
&[],
|
||||
) {
|
||||
env.exception_describe();
|
||||
env.exception_clear();
|
||||
return Err(anyhow::anyhow!("Failed to setup PatchBukkit server: {e:?}"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,52 +1,73 @@
|
||||
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
||||
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player};
|
||||
use crate::proto::patchbukkit::{
|
||||
abilities::{Abilities, SetAbilitiesRequest},
|
||||
common::Uuid,
|
||||
};
|
||||
|
||||
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let player_uuid = uuid::Uuid::parse_str(&request.value).ok()?;
|
||||
let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?;
|
||||
let abilities = tokio::task::block_in_place(|| {
|
||||
ctx.runtime
|
||||
.block_on(async { player.abilities.lock().await })
|
||||
});
|
||||
with_player(Some(&request), |player| {
|
||||
let abilities = match player.abilities.try_lock() {
|
||||
Ok(guard) => 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,
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
acquired.unwrap_or(Abilities {
|
||||
invulnerable: false,
|
||||
flying: false,
|
||||
allow_flying: false,
|
||||
creative: false,
|
||||
allow_modify_world: true,
|
||||
fly_speed: 0.05,
|
||||
walk_speed: 0.1,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Some(Abilities {
|
||||
invulnerable: abilities.invulnerable,
|
||||
flying: abilities.flying,
|
||||
allow_flying: abilities.allow_flying,
|
||||
creative: abilities.creative,
|
||||
allow_modify_world: abilities.allow_modify_world,
|
||||
fly_speed: abilities.fly_speed,
|
||||
walk_speed: abilities.walk_speed,
|
||||
})
|
||||
Some(abilities)
|
||||
})?
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let player_uuid = uuid::Uuid::parse_str(&request.uuid?.value).ok()?;
|
||||
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();
|
||||
ctx.runtime.spawn(async move {
|
||||
{
|
||||
let mut pumpkin_abilities = player.abilities.lock().await;
|
||||
pumpkin_abilities.invulnerable = abilities.invulnerable;
|
||||
pumpkin_abilities.flying = abilities.flying;
|
||||
pumpkin_abilities.allow_flying = abilities.allow_flying;
|
||||
pumpkin_abilities.creative = abilities.creative;
|
||||
pumpkin_abilities.allow_modify_world = abilities.allow_modify_world;
|
||||
pumpkin_abilities.fly_speed = abilities.fly_speed;
|
||||
pumpkin_abilities.walk_speed = abilities.walk_speed;
|
||||
}
|
||||
player.send_abilities_update().await;
|
||||
});
|
||||
|
||||
let mut pumpkin_abilities = tokio::task::block_in_place(|| {
|
||||
ctx.runtime
|
||||
.block_on(async { player.abilities.lock().await })
|
||||
});
|
||||
|
||||
pumpkin_abilities.invulnerable = abilities.invulnerable;
|
||||
pumpkin_abilities.flying = abilities.flying;
|
||||
pumpkin_abilities.allow_flying = abilities.allow_flying;
|
||||
pumpkin_abilities.creative = abilities.creative;
|
||||
pumpkin_abilities.allow_modify_world = abilities.allow_modify_world;
|
||||
pumpkin_abilities.fly_speed = abilities.fly_speed;
|
||||
pumpkin_abilities.walk_speed = abilities.walk_speed;
|
||||
|
||||
let player = player.clone();
|
||||
ctx.runtime.spawn(async move {
|
||||
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};
|
||||
|
||||
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 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 plugin_context = context.plugin_context.clone();
|
||||
|
||||
tracing::info!(
|
||||
"Registering Bukkit command '{}' from plugin '{}' (aliases: {:?})",
|
||||
cmd_name,
|
||||
plugin_name,
|
||||
aliases
|
||||
);
|
||||
|
||||
context.runtime.spawn(async move {
|
||||
let mut raw_names: Vec<String> = vec![cmd_name.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 permission = format!("patchbukkit:command.{clean_perm}");
|
||||
|
||||
let registry = {
|
||||
let permission_manager = plugin_context.permission_manager.read().await;
|
||||
permission_manager.registry.clone()
|
||||
};
|
||||
let registry = plugin_context.server.permission_manager.clone();
|
||||
|
||||
let _ = registry.write().await.register_permission(Permission::new(
|
||||
if let Err(e) = registry.register_permission(Permission::new(
|
||||
&permission,
|
||||
&format!("Allows running command `{cmd_name}` from `{plugin_name}`"),
|
||||
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(())
|
||||
|
||||
@ -12,12 +12,8 @@ use crate::{
|
||||
};
|
||||
|
||||
pub fn ffi_native_bridge_get_entity_health_impl(request: Uuid) -> Option<EntityHealthResponse> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
with_player(Some(&request), |player| {
|
||||
let health = tokio::task::block_in_place(|| {
|
||||
ctx.runtime
|
||||
.block_on(async { f64::from(player.living_entity.health.load()) })
|
||||
});
|
||||
let health = f64::from(player.living_entity.health.load());
|
||||
|
||||
EntityHealthResponse {
|
||||
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 mut is_op = perm_lvl >= op_lvl || perm_lvl > pumpkin_util::PermissionLvl::Zero;
|
||||
|
||||
if !is_op {
|
||||
let op_config = tokio::task::block_in_place(|| {
|
||||
ctx.runtime
|
||||
.block_on(async { ctx.plugin_context.server.data.operator_config.read().await })
|
||||
});
|
||||
if op_config.get_entry(&player_uuid).is_some() {
|
||||
is_op = true;
|
||||
}
|
||||
if !is_op
|
||||
&& let Ok(op_config) = ctx.plugin_context.server.data.operator_config.try_read()
|
||||
&& op_config.get_entry(&player_uuid).is_some()
|
||||
{
|
||||
is_op = true;
|
||||
}
|
||||
|
||||
is_op
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let op_config = tokio::task::block_in_place(|| {
|
||||
ctx.runtime
|
||||
.block_on(async { ctx.plugin_context.server.data.operator_config.read().await })
|
||||
});
|
||||
op_config.get_entry(&player_uuid).is_some()
|
||||
ctx.plugin_context
|
||||
.server
|
||||
.data
|
||||
.operator_config
|
||||
.try_read()
|
||||
.is_ok_and(|ops| ops.get_entry(&player_uuid).is_some())
|
||||
});
|
||||
|
||||
Some(crate::proto::patchbukkit::entity::IsOpResponse { is_op })
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack as PumpkinItemStack;
|
||||
use tokio::task::block_in_place;
|
||||
|
||||
use crate::{
|
||||
java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player},
|
||||
@ -55,34 +54,44 @@ fn proto_item_to_pumpkin(proto: Option<&ProtoItemStack>) -> PumpkinItemStack {
|
||||
pub fn ffi_native_bridge_get_player_inventory_impl(
|
||||
request: Uuid,
|
||||
) -> Option<GetPlayerInventoryResponse> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
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 = main_guard.iter().map(pumpkin_item_to_proto).collect();
|
||||
let main_inventory = player
|
||||
.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 = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::OFF_HAND));
|
||||
let helmet = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::HEAD));
|
||||
let chestplate = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::CHEST));
|
||||
let leggings = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::LEGS));
|
||||
let boots = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::FEET));
|
||||
let (off_hand, helmet, chestplate, leggings, boots) =
|
||||
if let Ok(eq_guard) = player.inventory.entity_equipment.try_lock() {
|
||||
(
|
||||
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::OFF_HAND)),
|
||||
pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::HEAD)),
|
||||
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 {
|
||||
main_inventory,
|
||||
selected_slot,
|
||||
off_hand: Some(off_hand),
|
||||
helmet: Some(helmet),
|
||||
chestplate: Some(chestplate),
|
||||
leggings: Some(leggings),
|
||||
boots: Some(boots),
|
||||
}
|
||||
})
|
||||
})
|
||||
GetPlayerInventoryResponse {
|
||||
main_inventory,
|
||||
selected_slot,
|
||||
off_hand: Some(off_hand),
|
||||
helmet: Some(helmet),
|
||||
chestplate: Some(chestplate),
|
||||
leggings: Some(leggings),
|
||||
boots: Some(boots),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -4,12 +4,17 @@ pub fn ffi_native_bridge_send_log_impl(request: SendLogRequest) -> Option<()> {
|
||||
let logger = &request.logger_name;
|
||||
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() {
|
||||
LogLevel::Severe => tracing::error!(logger = %logger, "{}", msg),
|
||||
LogLevel::Warning => tracing::warn!(logger = %logger, "{}", msg),
|
||||
LogLevel::Info => tracing::info!(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::Finest => tracing::trace!(logger = %logger, "{}", msg),
|
||||
LogLevel::Off => {}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use j4rs::Jvm;
|
||||
use pumpkin::plugin::Context;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@ -76,6 +75,6 @@ pub fn init_callback_context(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
||||
initialize_ffi_callbacks(jvm)
|
||||
pub fn initialize_callbacks(env: &mut jni::Env) -> Result<()> {
|
||||
initialize_ffi_callbacks(env)
|
||||
}
|
||||
|
||||
@ -38,8 +38,7 @@ pub fn ffi_native_bridge_player_entity_play_sound_impl(
|
||||
let entity = entity.first()?.get_entity();
|
||||
|
||||
player
|
||||
.client
|
||||
.enqueue_packet(&CEntitySoundEffect::new(
|
||||
.send_client_packet(&CEntitySoundEffect::new(
|
||||
IdOr::Id(pumpkin_sound as u16),
|
||||
category,
|
||||
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() }
|
||||
}
|
||||
|
||||
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>
|
||||
where
|
||||
F: FnOnce(Arc<Player>) -> R,
|
||||
|
||||
@ -100,7 +100,7 @@ pub fn ffi_native_bridge_get_world_border_impl(
|
||||
.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 {
|
||||
center_x: wb.center_x,
|
||||
@ -131,17 +131,19 @@ pub fn ffi_native_bridge_set_world_border_impl(
|
||||
.cloned()
|
||||
.or_else(|| worlds.first().cloned())?;
|
||||
|
||||
let mut wb = world.worldborder.blocking_lock();
|
||||
wb.center_x = border_data.center_x;
|
||||
wb.center_z = border_data.center_z;
|
||||
wb.old_diameter = border_data.size;
|
||||
wb.new_diameter = border_data.target_size;
|
||||
wb.speed = border_data.speed;
|
||||
wb.warning_time = border_data.warning_time;
|
||||
wb.warning_blocks = border_data.warning_blocks;
|
||||
wb.damage_per_block = border_data.damage_per_block as f32;
|
||||
wb.buffer = border_data.damage_buffer as f32;
|
||||
wb.portal_teleport_boundary = border_data.max_center_coordinate;
|
||||
ctx.runtime.spawn(async move {
|
||||
let mut wb = world.worldborder.lock().await;
|
||||
wb.center_x = border_data.center_x;
|
||||
wb.center_z = border_data.center_z;
|
||||
wb.old_diameter = border_data.size;
|
||||
wb.new_diameter = border_data.target_size;
|
||||
wb.speed = border_data.speed;
|
||||
wb.warning_time = border_data.warning_time;
|
||||
wb.warning_blocks = border_data.warning_blocks;
|
||||
wb.damage_per_block = border_data.damage_per_block as f32;
|
||||
wb.buffer = border_data.damage_buffer as f32;
|
||||
wb.portal_teleport_boundary = border_data.max_center_coordinate;
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
@ -1,26 +1,11 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use j4rs::{Instance, InvocationArg, Jvm};
|
||||
use pumpkin::{command::dispatcher::CommandError, plugin::Context};
|
||||
use jni::Env;
|
||||
use pumpkin::command::dispatcher::CommandError;
|
||||
use pumpkin_protocol::java::client::play::CommandSuggestion;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
commands::{SimpleCommandSender, init_java_command},
|
||||
config,
|
||||
java::{
|
||||
jvm::commands::{JvmCommand, Location},
|
||||
plugin::manager::Plugin,
|
||||
},
|
||||
};
|
||||
use crate::{commands::SimpleCommandSender, java::jvm::commands::Location};
|
||||
|
||||
const PATCHBUKKIT_PERMISSION_NAMESPACE: &str = "patchbukkit";
|
||||
|
||||
pub struct CommandManager {
|
||||
command_map: Option<Instance>,
|
||||
}
|
||||
pub struct CommandManager;
|
||||
|
||||
impl Default for CommandManager {
|
||||
fn default() -> Self {
|
||||
@ -31,39 +16,17 @@ impl Default for CommandManager {
|
||||
impl CommandManager {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self { command_map: None }
|
||||
}
|
||||
|
||||
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())
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn get_tab_complete(
|
||||
&mut self,
|
||||
jvm: &Jvm,
|
||||
env: &mut Env,
|
||||
sender: SimpleCommandSender,
|
||||
full_command: String,
|
||||
location: Option<Location>,
|
||||
) -> 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),
|
||||
Err(e) => {
|
||||
tracing::warn!("Tab completion failed: {e}");
|
||||
@ -74,271 +37,106 @@ impl CommandManager {
|
||||
|
||||
fn try_tab_complete(
|
||||
&mut self,
|
||||
jvm: &Jvm,
|
||||
env: &mut Env,
|
||||
sender: SimpleCommandSender,
|
||||
full_command: String,
|
||||
location: Option<Location>,
|
||||
) -> Result<Option<Vec<CommandSuggestion>>> {
|
||||
let command_map = self.get_or_init_command_map(jvm)?;
|
||||
|
||||
let sender = Self::sender_to_jsender(jvm, sender)?;
|
||||
|
||||
let completions = if let Some(location) = location {
|
||||
let world = jvm.invoke_static(
|
||||
"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
|
||||
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,
|
||||
),
|
||||
permission_default,
|
||||
)) {
|
||||
if e.contains("already registered") {
|
||||
tracing::debug!(
|
||||
"Permission already registered for command {}: {}",
|
||||
cmd_name,
|
||||
e
|
||||
);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Failed to register permission for command {}: {:?}",
|
||||
cmd_name,
|
||||
e
|
||||
);
|
||||
};
|
||||
|
||||
let cmd_jstr = env.new_string(&full_command)?;
|
||||
let (world_jstr, x, y, z) = match location {
|
||||
Some(loc) => (
|
||||
Some(env.new_string(loc.world.to_string())?),
|
||||
loc.x,
|
||||
loc.y,
|
||||
loc.z,
|
||||
),
|
||||
None => (None, 0.0, 0.0, 0.0),
|
||||
};
|
||||
|
||||
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(())
|
||||
Ok(Some(suggestions))
|
||||
}
|
||||
|
||||
pub fn trigger_command(
|
||||
&mut self,
|
||||
jvm: &Jvm,
|
||||
env: &mut Env,
|
||||
full_command: String,
|
||||
sender: SimpleCommandSender,
|
||||
) -> 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(
|
||||
command_map,
|
||||
"dispatch",
|
||||
let result = env.call_static_method(
|
||||
jni::jni_str!("org/patchbukkit/command/PatchBukkitCommandMap"),
|
||||
jni::jni_str!("dispatchRaw"),
|
||||
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Z"),
|
||||
&[
|
||||
InvocationArg::from(j_sender),
|
||||
InvocationArg::try_from(full_command.clone())?,
|
||||
uuid_val,
|
||||
name_val,
|
||||
jni::objects::JValue::Bool(is_op),
|
||||
(&cmd_jstr).into(),
|
||||
],
|
||||
);
|
||||
|
||||
match dispatch_result {
|
||||
Ok(result) => {
|
||||
let handled: bool = jvm.to_rust(result)?;
|
||||
match result {
|
||||
Ok(val) => {
|
||||
let handled = val.z()?;
|
||||
if !handled {
|
||||
tracing::warn!("Command not handled by any Java plugin: {full_command}");
|
||||
}
|
||||
@ -350,73 +148,16 @@ impl CommandManager {
|
||||
|
||||
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(
|
||||
cmd_name: &str,
|
||||
cmd_data: &config::spigot::Command,
|
||||
) -> (String, PermissionDefault) {
|
||||
cmd_data: &crate::config::spigot::Command,
|
||||
) -> (String, pumpkin_util::permission::PermissionDefault) {
|
||||
let clean = cmd_name.trim_start_matches('/');
|
||||
if let Some(permission) = &cmd_data.permission {
|
||||
let perm_node = if permission.contains(':') {
|
||||
@ -424,11 +165,14 @@ fn build_permission_node(
|
||||
} else {
|
||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:{permission}")
|
||||
};
|
||||
(perm_node, PermissionDefault::Allow)
|
||||
(
|
||||
perm_node,
|
||||
pumpkin_util::permission::PermissionDefault::Allow,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{clean}"),
|
||||
PermissionDefault::Allow,
|
||||
pumpkin_util::permission::PermissionDefault::Allow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use j4rs::{Instance, InvocationArg, Jvm};
|
||||
use jni::Env;
|
||||
use prost::Message;
|
||||
use pumpkin::{entity::player::Player, server::Server};
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
proto::patchbukkit::events::{FireEventResponse, event::Data},
|
||||
};
|
||||
|
||||
pub struct EventManager {}
|
||||
pub struct EventManager;
|
||||
|
||||
impl Default for EventManager {
|
||||
fn default() -> Self {
|
||||
@ -21,144 +21,74 @@ impl Default for EventManager {
|
||||
impl EventManager {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn fire_event(
|
||||
&self,
|
||||
jvm: &Jvm,
|
||||
env: &mut Env,
|
||||
payload: JvmEventPayload,
|
||||
plugin_name: String,
|
||||
) -> 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
|
||||
&& matches!(event, Data::PlayerJoin(_))
|
||||
&& 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 j_bytes = jvm.create_java_array(
|
||||
"byte",
|
||||
&bytes
|
||||
.iter()
|
||||
.map(|&b| {
|
||||
InvocationArg::try_from(b as i8)
|
||||
.unwrap()
|
||||
.into_primitive()
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
let j_bytes = env.byte_array_from_slice(&bytes)?;
|
||||
let plugin_name_jstr = env.new_string(&plugin_name)?;
|
||||
|
||||
let result = env.call_static_method(
|
||||
jni::jni_str!("org/patchbukkit/events/PatchBukkitEventFactory"),
|
||||
jni::jni_str!("fireEventFromBytes"),
|
||||
jni::jni_sig!("([BLjava/lang/String;)[B"),
|
||||
&[(&j_bytes).into(), (&plugin_name_jstr).into()],
|
||||
)?;
|
||||
|
||||
let j_event = jvm.invoke_static(
|
||||
"org.patchbukkit.events.PatchBukkitEventFactory",
|
||||
"createEventFromBytes",
|
||||
&[InvocationArg::from(j_bytes)],
|
||||
)?;
|
||||
|
||||
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"
|
||||
));
|
||||
let obj = result.l()?;
|
||||
if obj.is_null() {
|
||||
return Ok(FireEventResponse {
|
||||
cancelled: false,
|
||||
data: Some(payload.event),
|
||||
});
|
||||
}
|
||||
|
||||
jvm.invoke(
|
||||
&event_manager,
|
||||
"fireEvent",
|
||||
&[
|
||||
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())?;
|
||||
|
||||
let j_byte_array = jni::objects::JByteArray::cast_local(env, obj)?;
|
||||
let resp_vec = env.convert_byte_array(&j_byte_array)?;
|
||||
let response = FireEventResponse::decode(resp_vec.as_slice())?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn register_player(
|
||||
jvm: &Jvm,
|
||||
patch_server: &Instance,
|
||||
env: &mut Env,
|
||||
player: &Arc<Player>,
|
||||
_server: &Arc<Server>,
|
||||
) -> Result<()> {
|
||||
let j_uuid = jvm
|
||||
.invoke_static(
|
||||
"java.util.UUID",
|
||||
"fromString",
|
||||
&[InvocationArg::try_from(player.gameprofile.id.to_string())?],
|
||||
)
|
||||
.map_err(|e| format!("Failed to create Java UUID: {e}"))
|
||||
.unwrap();
|
||||
let player_permission_level = player.permission_lvl.load();
|
||||
let is_op = player_permission_level > pumpkin_util::permission::PermissionLvl::Zero
|
||||
|| _server
|
||||
.data
|
||||
.operator_config
|
||||
.try_read()
|
||||
.is_ok_and(|ops| ops.get_entry(&player.gameprofile.id).is_some());
|
||||
|
||||
let j_player = jvm.create_instance(
|
||||
"org.patchbukkit.entity.PatchBukkitPlayer",
|
||||
let uuid_jstr = env.new_string(player.gameprofile.id.to_string())?;
|
||||
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),
|
||||
InvocationArg::try_from(player.gameprofile.name.clone())?,
|
||||
(&uuid_jstr).into(),
|
||||
(&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(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use j4rs::{Instance, InvocationArg, Jvm};
|
||||
use jni::Env;
|
||||
use pumpkin::plugin::Context;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@ -64,12 +64,9 @@ pub struct Plugin {
|
||||
pub state: PluginState,
|
||||
/// Data folder for this plugin
|
||||
pub data_folder: PathBuf,
|
||||
pub instance: Option<Instance>,
|
||||
// The registered commands
|
||||
pub commands: HashMap<String, Command>,
|
||||
|
||||
pub listeners: HashMap<String, Instance>,
|
||||
|
||||
// Dependency metadata (normalized to lowercase)
|
||||
pub provides: Vec<String>,
|
||||
pub depends: Vec<String>,
|
||||
@ -257,9 +254,7 @@ impl PluginManager {
|
||||
state: PluginState::Registered,
|
||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||
path: jar_path.as_ref().to_path_buf(),
|
||||
instance: None,
|
||||
commands,
|
||||
listeners: HashMap::new(),
|
||||
|
||||
provides,
|
||||
depends: dedupe_names(depends),
|
||||
@ -306,9 +301,7 @@ impl PluginManager {
|
||||
state: PluginState::Registered,
|
||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||
path: jar_path.as_ref().to_path_buf(),
|
||||
instance: None,
|
||||
commands,
|
||||
listeners: HashMap::new(),
|
||||
|
||||
provides,
|
||||
depends: dedupe_names(depends),
|
||||
@ -323,30 +316,43 @@ impl PluginManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
||||
pub fn enable_all_plugins(&mut self, _env: &mut Env) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
||||
pub fn disable_all_plugins(&mut self, _env: &mut Env) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn instantiate_all_plugins(
|
||||
pub fn instantiate_all_plugins(
|
||||
&mut self,
|
||||
jvm: &Jvm,
|
||||
env: &mut Env,
|
||||
plugin_folder: &Path,
|
||||
_server: &Arc<Context>,
|
||||
_command_tx: mpsc::Sender<JvmCommand>,
|
||||
_command_manager: &mut CommandManager,
|
||||
) -> Result<()> {
|
||||
tracing::info!("Bootstrapping Java plugins from {:?}", plugin_folder);
|
||||
let _ = jvm.invoke_static(
|
||||
"org.patchbukkit.loader.PatchBukkitBootstrap",
|
||||
"bootstrapPlugins",
|
||||
&[InvocationArg::try_from(
|
||||
&plugin_folder.to_string_lossy().to_string(),
|
||||
)?],
|
||||
let path_str = plugin_folder.to_string_lossy();
|
||||
let j_str = env.new_string(&path_str)?;
|
||||
let result = env.call_static_method(
|
||||
jni::jni_str!("org/patchbukkit/loader/PatchBukkitBootstrap"),
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@ -8,39 +8,49 @@ use anyhow::Result;
|
||||
use rust_embed::Embed;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[folder = "resources/"]
|
||||
#[folder = "resources/jassets/"]
|
||||
pub struct Resources;
|
||||
|
||||
pub fn setup_j4rs(j4rs_folder: &Path) -> Result<()> {
|
||||
sync_embedded_resources(j4rs_folder)?;
|
||||
cleanup_stale_files(j4rs_folder);
|
||||
pub fn setup_resources(resources_folder: &Path) -> Result<()> {
|
||||
sync_embedded_resources(resources_folder)?;
|
||||
cleanup_stale_files(resources_folder);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_stale_files(j4rs_folder: &Path) {
|
||||
fn cleanup_stale_files(resources_folder: &Path) {
|
||||
let embedded_paths: HashSet<PathBuf> = Resources::iter()
|
||||
.map(|p| PathBuf::from(p.to_string()))
|
||||
.collect();
|
||||
|
||||
for entry in walkdir::WalkDir::new(j4rs_folder)
|
||||
for entry in walkdir::WalkDir::new(resources_folder)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
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)
|
||||
{
|
||||
tracing::warn!("Removing stale embedded file: {}", rel_path.display());
|
||||
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() {
|
||||
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();
|
||||
|
||||
if resource_path.exists() {
|
||||
|
||||
145
rust/src/lib.rs
145
rust/src/lib.rs
@ -13,17 +13,13 @@ pub mod java;
|
||||
pub mod proto;
|
||||
|
||||
use directories::setup_directories;
|
||||
use java::jar::discover_jar_files;
|
||||
use tokio::sync::{
|
||||
mpsc::{self, Receiver},
|
||||
oneshot,
|
||||
};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::{
|
||||
config::patchbukkit::PatchBukkitConfig,
|
||||
java::{
|
||||
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}"))?;
|
||||
|
||||
// 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();
|
||||
plugin
|
||||
.command_tx
|
||||
if let Err(e) = command_tx
|
||||
.send(JvmCommand::Initialize {
|
||||
j4rs_path: dirs.j4rs,
|
||||
jassets_path: dirs.jassets.clone(),
|
||||
respond_to: tx,
|
||||
context: server.clone(),
|
||||
command_tx: plugin.command_tx.clone(),
|
||||
context: server_clone.clone(),
|
||||
runtime_handle,
|
||||
command_tx: command_tx.clone(),
|
||||
config,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send command to initialize J4RS: {e}"))?;
|
||||
rx.await
|
||||
.map_err(|e| format!("Unable to receive response from J4RS initialization: {e}"))?
|
||||
.map_err(|e| format!("Failed to initialize all plugins: {e}"))?;
|
||||
}
|
||||
{
|
||||
tracing::error!("Failed to send command to initialize JVM: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
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();
|
||||
plugin
|
||||
.command_tx
|
||||
if let Err(e) = command_tx
|
||||
.send(JvmCommand::InstantiateAllPlugins {
|
||||
plugins_dir: dirs.plugins.clone(),
|
||||
respond_to: tx,
|
||||
server: server.clone(),
|
||||
command_tx: plugin.command_tx.clone(),
|
||||
server: server_clone.clone(),
|
||||
command_tx: command_tx.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send command to instantiate plugins: {e}"))?;
|
||||
rx.await
|
||||
.map_err(|e| format!("Unable to receive response from instantiate plugins: {e}"))?
|
||||
.map_err(|e| format!("Failed to instantiate all plugins: {e}"))?;
|
||||
}
|
||||
{
|
||||
tracing::error!("Failed to send command to instantiate plugins: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
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();
|
||||
plugin
|
||||
.command_tx
|
||||
if let Err(e) = command_tx
|
||||
.send(JvmCommand::EnableAllPlugins { respond_to: tx })
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send command to enable all plugins: {e}"))?;
|
||||
rx.await
|
||||
.map_err(|e| format!("Unable to receive response from enable all plugins: {e}"))?
|
||||
.map_err(|e| format!("Failed to enable all plugins: {e}"))?;
|
||||
};
|
||||
{
|
||||
tracing::error!("Failed to send command to enable all plugins: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register Bukkit plugin loader with Pumpkin so Bukkit/Paper plugins appear in Pumpkin's native /plugins command
|
||||
let bukkit_loader = Arc::new(crate::java::plugin::loader::BukkitPluginLoader);
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.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;
|
||||
match rx.await {
|
||||
Ok(Ok(())) => tracing::info!("PatchBukkit background initialization complete"),
|
||||
Ok(Err(e)) => tracing::error!("Failed to enable all plugins: {e}"),
|
||||
Err(e) => tracing::error!("Unable to receive response from enable all plugins: {e}"),
|
||||
}
|
||||
});
|
||||
|
||||
tracing::info!("PatchBukkit loaded successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -155,25 +165,36 @@ async fn on_unload(&self, server: Arc<Context>) -> Result<(), String> {
|
||||
#[plugin_impl]
|
||||
pub struct PatchBukkitPlugin {
|
||||
pub command_tx: mpsc::Sender<JvmCommand>,
|
||||
pub runtime: Arc<tokio::runtime::Runtime>,
|
||||
}
|
||||
|
||||
impl PatchBukkitPlugin {
|
||||
#[must_use]
|
||||
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]
|
||||
pub async fn jvm_thread_task(rx: Receiver<JvmCommand>) {
|
||||
JvmWorker::new(rx).attach_thread().await;
|
||||
}
|
||||
let (tx, rx) = mpsc::channel(4096);
|
||||
|
||||
let runtime_clone = runtime.clone();
|
||||
std::thread::Builder::new()
|
||||
.name("patchbukkit-jvm-worker".to_string())
|
||||
.spawn(move || {
|
||||
jvm_thread_task(rx);
|
||||
runtime_clone.block_on(async move {
|
||||
JvmWorker::new(rx).attach_thread().await;
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
Self { command_tx: tx }
|
||||
Self {
|
||||
command_tx: tx,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user