many fixes
This commit is contained in:
parent
3a9052879a
commit
afb0e82713
BIN
java/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
java/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
6
java/gradlew
vendored
6
java/gradlew
vendored
@ -20,7 +20,7 @@
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
# gradlew start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
@ -29,7 +29,7 @@
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
# ksh gradlew
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
35
java/gradlew.bat
vendored
35
java/gradlew.bat
vendored
@ -19,12 +19,12 @@
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem gradlew startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@ -51,7 +51,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@ -65,29 +65,18 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
@rem Execute gradlew
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
|
||||
@ -10,7 +10,8 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:26.2.build.87-stable")
|
||||
compileOnly(project(":patchbukkit"))
|
||||
compileOnly("io.papermc.paper:paper-api:26.2.build.111-stable")
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@ -90,4 +90,43 @@ public final class CommandSystemTests {
|
||||
assertNotNull(found, "CommandMap.getCommand(\"myprefix:pbtestalias\")");
|
||||
}
|
||||
|
||||
@ConformanceTest(name = "CommandMap supports WorldEdit double-slash commands", category = TestCategory.COMMAND_SYSTEM)
|
||||
public void testDoubleSlashCommands() {
|
||||
CommandMap map = Bukkit.getServer().getCommandMap();
|
||||
final String[] executedLabel = {null};
|
||||
Command testCmd = new BukkitCommand("//wand", "wand", "//wand", List.of("//set", "set")) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String label, String[] args) {
|
||||
executedLabel[0] = label;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
map.register("worldedit", testCmd);
|
||||
|
||||
CommandSender sender = Bukkit.getConsoleSender();
|
||||
|
||||
// 1. Dispatch with double slash
|
||||
boolean resultDouble = map.dispatch(sender, "//wand");
|
||||
assertTrue(resultDouble, "dispatch() with double slash should return true");
|
||||
assertTrue("//wand".equals(executedLabel[0]), "executed label for //wand");
|
||||
|
||||
// 2. Dispatch with single slash
|
||||
boolean resultSingle = map.dispatch(sender, "/wand");
|
||||
assertTrue(resultSingle, "dispatch() with single slash should return true");
|
||||
|
||||
// 3. Dispatch without slash
|
||||
boolean resultNoSlash = map.dispatch(sender, "wand");
|
||||
assertTrue(resultNoSlash, "dispatch() without slash should return true");
|
||||
|
||||
// 4. Dispatch alias with double slash
|
||||
boolean resultAlias = map.dispatch(sender, "//set");
|
||||
assertTrue(resultAlias, "dispatch() with double slash alias should return true");
|
||||
|
||||
// 5. getCommand lookups
|
||||
assertNotNull(map.getCommand("//wand"), "getCommand(\"//wand\")");
|
||||
assertNotNull(map.getCommand("/wand"), "getCommand(\"/wand\")");
|
||||
assertNotNull(map.getCommand("wand"), "getCommand(\"wand\")");
|
||||
assertNotNull(map.getCommand("//set"), "getCommand(\"//set\")");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -43,4 +43,30 @@ public final class EntityTests {
|
||||
}
|
||||
}
|
||||
|
||||
@ConformanceTest(name = "Player ground state and velocity getters run cleanly", category = TestCategory.ENTITY)
|
||||
public void testPlayerGroundStateAndVelocity() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
boolean onGround = p.isOnGround();
|
||||
org.bukkit.util.Vector vel = p.getVelocity();
|
||||
assertNotNull(onGround, "Player.isOnGround()");
|
||||
assertNotNull(vel, "Player.getVelocity()");
|
||||
}
|
||||
}
|
||||
|
||||
@ConformanceTest(name = "Player inventory and equipment getters and setters run cleanly", category = TestCategory.ENTITY)
|
||||
public void testPlayerInventoryAndEquipment() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
org.bukkit.inventory.PlayerInventory inv = p.getInventory();
|
||||
org.bukkit.inventory.EntityEquipment eq = p.getEquipment();
|
||||
assertNotNull(inv, "Player.getInventory()");
|
||||
assertNotNull(eq, "Player.getEquipment()");
|
||||
assertNotNull(inv.getItemInMainHand(), "PlayerInventory.getItemInMainHand()");
|
||||
assertNotNull(inv.getItemInOffHand(), "PlayerInventory.getItemInOffHand()");
|
||||
assertNotNull(inv.getArmorContents(), "PlayerInventory.getArmorContents()");
|
||||
assertTrue(inv.getArmorContents().length == 4, "PlayerInventory.getArmorContents().length == 4");
|
||||
assertNotNull(inv.getContents(), "PlayerInventory.getContents()");
|
||||
assertTrue(inv.getContents().length == 41, "PlayerInventory.getContents().length == 41");
|
||||
assertTrue(inv.getHeldItemSlot() >= 0 && inv.getHeldItemSlot() < 9, "PlayerInventory.getHeldItemSlot() valid slot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,4 +119,11 @@ public final class StubTests {
|
||||
Bukkit.getServer().getAllowNether();
|
||||
}
|
||||
|
||||
@ConformanceTest(name = "PaperLib detection classes and PaperLib.isPaper() are available", category = TestCategory.STUBS)
|
||||
public void testPaperLibDetection() throws ClassNotFoundException {
|
||||
Class.forName("com.destroystokyo.paper.PaperConfig");
|
||||
Class.forName("io.papermc.paper.configuration.Configuration");
|
||||
org.patchbukkit.testplugin.TestAssertions.assertTrue(io.papermc.lib.PaperLib.isPaper(), "PaperLib.isPaper() must be true");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
id("com.google.protobuf") version "0.10.0"
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT"
|
||||
}
|
||||
|
||||
val protobufVersion = "4.35.1"
|
||||
@ -38,6 +39,7 @@ tasks.named("generateProto") {
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "papermc"
|
||||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||||
@ -45,7 +47,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("io.papermc.paper:paper-api:26.2.build.87-stable")
|
||||
paperweight.paperDevBundle("26.2.build.111-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")
|
||||
@ -55,6 +57,12 @@ dependencies {
|
||||
implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18")
|
||||
implementation("com.google.protobuf:protobuf-java:$protobufVersion")
|
||||
implementation("org.slf4j:slf4j-jdk14:2.0.16")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
java {
|
||||
@ -73,12 +81,12 @@ tasks.withType<JavaCompile> {
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
dependsOn(configurations.runtimeClasspath)
|
||||
dependsOn(configurations.compileClasspath)
|
||||
|
||||
from({
|
||||
configurations.runtimeClasspath.get()
|
||||
.filter { it.name.endsWith(".jar") }
|
||||
.map { zipTree(it) }
|
||||
configurations.compileClasspath.get().map { file ->
|
||||
if (file.isDirectory) file else zipTree(file)
|
||||
}
|
||||
})
|
||||
|
||||
exclude(
|
||||
|
||||
0
java/patchbukkit/logs/latest.log
Normal file
0
java/patchbukkit/logs/latest.log
Normal file
@ -0,0 +1,7 @@
|
||||
package com.destroystokyo.paper;
|
||||
|
||||
/**
|
||||
* Stub class for PaperLib and Paper plugin detection (PaperLib.isPaper()).
|
||||
*/
|
||||
public class PaperConfig {
|
||||
}
|
||||
52
java/patchbukkit/src/main/java/io/papermc/lib/PaperLib.java
Normal file
52
java/patchbukkit/src/main/java/io/papermc/lib/PaperLib.java
Normal file
@ -0,0 +1,52 @@
|
||||
package io.papermc.lib;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Stub / implementation of PaperLib for Paper plugin compatibility.
|
||||
*/
|
||||
public class PaperLib {
|
||||
|
||||
private PaperLib() {
|
||||
}
|
||||
|
||||
public static boolean isPaper() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isSpigot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isVersion(int version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isVersion(int version, int patch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getMinecraftVersion() {
|
||||
return 26;
|
||||
}
|
||||
|
||||
public static int getMinecraftPatchVersion() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static void suggestPaper(Plugin plugin) {
|
||||
}
|
||||
|
||||
public static void suggestPaper(Plugin plugin, String reason) {
|
||||
}
|
||||
|
||||
public static void suggestPaper(Plugin plugin, Level level, String reason) {
|
||||
}
|
||||
|
||||
public static void suggestPaper(Plugin plugin, String reason, Object... args) {
|
||||
}
|
||||
|
||||
public static void suggestPaper(Plugin plugin, Level level, String reason, Object... args) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
/**
|
||||
* Stub class for PaperLib and Paper plugin detection (PaperLib.isPaper()).
|
||||
*/
|
||||
public class Configuration {
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
package org.bukkit.command;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SimpleCommandMap implements CommandMap {
|
||||
protected final Map<String, Command> knownCommands;
|
||||
protected final Server server;
|
||||
|
||||
public SimpleCommandMap(@NotNull Server server, @NotNull Map<String, Command> knownCommands) {
|
||||
this.server = server;
|
||||
this.knownCommands = knownCommands != null ? knownCommands : new HashMap<>();
|
||||
}
|
||||
|
||||
public SimpleCommandMap(@NotNull Server server) {
|
||||
this(server, new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAll(@NotNull String fallbackPrefix, @NotNull List<Command> commands) {
|
||||
if (commands != null) {
|
||||
for (Command c : commands) {
|
||||
register(fallbackPrefix, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) {
|
||||
if (label == null || fallbackPrefix == null || command == null) {
|
||||
return false;
|
||||
}
|
||||
label = label.toLowerCase().trim();
|
||||
fallbackPrefix = fallbackPrefix.toLowerCase().trim();
|
||||
|
||||
boolean registeredDirectly = false;
|
||||
if (!knownCommands.containsKey(label)) {
|
||||
knownCommands.put(label, command);
|
||||
registeredDirectly = true;
|
||||
}
|
||||
knownCommands.put(fallbackPrefix + ":" + label, command);
|
||||
|
||||
if (command.getAliases() != null) {
|
||||
for (String alias : command.getAliases()) {
|
||||
if (alias == null) continue;
|
||||
alias = alias.toLowerCase().trim();
|
||||
if (!knownCommands.containsKey(alias)) {
|
||||
knownCommands.put(alias, command);
|
||||
}
|
||||
knownCommands.put(fallbackPrefix + ":" + alias, command);
|
||||
}
|
||||
}
|
||||
return registeredDirectly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NotNull String fallbackPrefix, @NotNull Command command) {
|
||||
if (command == null) return false;
|
||||
return register(command.getName(), fallbackPrefix, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatch(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException {
|
||||
if (commandLine == null) return false;
|
||||
String cleanLine = commandLine.trim();
|
||||
while (cleanLine.startsWith("/")) {
|
||||
cleanLine = cleanLine.substring(1).trim();
|
||||
}
|
||||
if (cleanLine.isEmpty()) return false;
|
||||
|
||||
String[] split = cleanLine.split("\\s+");
|
||||
if (split.length == 0) return false;
|
||||
|
||||
String sentLabel = split[0].toLowerCase();
|
||||
Command command = knownCommands.get(sentLabel);
|
||||
if (command == null) return false;
|
||||
|
||||
try {
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
return command.execute(sender, sentLabel, args);
|
||||
} catch (Exception ex) {
|
||||
throw new CommandException("Unhandled exception executing '" + commandLine + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCommands() {
|
||||
knownCommands.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Command getCommand(@NotNull String name) {
|
||||
if (name == null) return null;
|
||||
return knownCommands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<String, Command> getKnownCommands() {
|
||||
return Collections.unmodifiableMap(knownCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine) {
|
||||
return tabComplete(sender, cmdLine, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) {
|
||||
if (cmdLine == null) return new ArrayList<>();
|
||||
String cleanLine = cmdLine.trim();
|
||||
while (cleanLine.startsWith("/")) {
|
||||
cleanLine = cleanLine.substring(1).trim();
|
||||
}
|
||||
String[] split = cleanLine.split(" ", -1);
|
||||
if (split.length == 0) return new ArrayList<>();
|
||||
|
||||
String label = split[0].toLowerCase();
|
||||
Command command = knownCommands.get(label);
|
||||
|
||||
if (split.length == 1) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
for (String key : knownCommands.keySet()) {
|
||||
if (key.startsWith(label)) completions.add(key);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
||||
if (command != null) {
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
return command.tabComplete(sender, label, args, location);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -50,21 +50,38 @@ public final class LibraryResolver {
|
||||
|
||||
RepositorySystem system = newRepositorySystem();
|
||||
RepositorySystemSession session = newSession(system, baseDir);
|
||||
List<RemoteRepository> repos = List.of(
|
||||
new RemoteRepository.Builder(
|
||||
"papermc",
|
||||
"default",
|
||||
"https://repo.papermc.io/repository/maven-public/"
|
||||
).build(),
|
||||
new RemoteRepository.Builder(
|
||||
"central",
|
||||
"default",
|
||||
"https://repo1.maven.org/maven2/"
|
||||
).build()
|
||||
);
|
||||
List<RemoteRepository> repos = new ArrayList<>(List.of(
|
||||
new RemoteRepository.Builder("papermc", "default", "https://repo.papermc.io/repository/maven-public/").build(),
|
||||
new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build(),
|
||||
new RemoteRepository.Builder("jitpack", "default", "https://jitpack.io/").build(),
|
||||
new RemoteRepository.Builder("codemc", "default", "https://repo.codemc.io/repository/maven-public/").build(),
|
||||
new RemoteRepository.Builder("enginehub", "default", "https://maven.enginehub.org/repo/").build(),
|
||||
new RemoteRepository.Builder("sonatype-snapshots", "default", "https://oss.sonatype.org/content/repositories/snapshots/").build(),
|
||||
new RemoteRepository.Builder("sonatype-releases", "default", "https://oss.sonatype.org/content/repositories/releases/").build(),
|
||||
new RemoteRepository.Builder("sponge", "default", "https://repo.spongepowered.org/repository/maven-public/").build(),
|
||||
new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases").build(),
|
||||
new RemoteRepository.Builder("spigotmc", "default", "https://hub.spigotmc.org/nexus/content/repositories/snapshots/").build(),
|
||||
new RemoteRepository.Builder("miraculixx-snapshots", "default", "https://repo.miraculixx.de/snapshots").build(),
|
||||
new RemoteRepository.Builder("miraculixx-releases", "default", "https://repo.miraculixx.de/releases").build()
|
||||
));
|
||||
|
||||
Set<String> coordsToResolve = new LinkedHashSet<>();
|
||||
int customRepoIdx = 0;
|
||||
for (String item : unique) {
|
||||
String trimmed = item;
|
||||
if (trimmed.startsWith("repo:")) {
|
||||
trimmed = trimmed.substring(5).trim();
|
||||
}
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
||||
customRepoIdx++;
|
||||
repos.add(new RemoteRepository.Builder("custom-repo-" + customRepoIdx, "default", trimmed).build());
|
||||
} else {
|
||||
coordsToResolve.add(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
Set<File> files = new LinkedHashSet<>();
|
||||
for (String coord : unique) {
|
||||
for (String coord : coordsToResolve) {
|
||||
try {
|
||||
Dependency dependency = new Dependency(
|
||||
new DefaultArtifact(coord),
|
||||
|
||||
@ -1,13 +1,107 @@
|
||||
package org.patchbukkit;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockType;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
class PatchBukkitBlockData {
|
||||
static BlockData newData(BlockType type, String data) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'newData'"
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
public final class PatchBukkitBlockData {
|
||||
|
||||
private PatchBukkitBlockData() {}
|
||||
|
||||
public static BlockData newData(Material material, BlockType type, String data) {
|
||||
final Material mat = material != null ? material : (type != null ? type.asMaterial() : null);
|
||||
if (mat == null || mat.isLegacy()) {
|
||||
throw new IllegalArgumentException("Invalid block material: " + (material != null ? material : data));
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> sharedConstants = Class.forName("net.minecraft.SharedConstants");
|
||||
sharedConstants.getMethod("tryDetectVersion").invoke(null);
|
||||
Class<?> bootstrap = Class.forName("net.minecraft.server.Bootstrap");
|
||||
bootstrap.getMethod("bootStrap").invoke(null);
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
try {
|
||||
if (data != null && !data.isEmpty()) {
|
||||
Class<?> craftBlockData = Class.forName("org.bukkit.craftbukkit.block.data.CraftBlockData");
|
||||
java.lang.reflect.Method fromString = craftBlockData.getMethod("fromString", BlockType.class, String.class);
|
||||
BlockData craftData = (BlockData) fromString.invoke(null, type, data);
|
||||
if (craftData != null) {
|
||||
return craftData;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
final String stateData;
|
||||
if (data != null && !data.isEmpty()) {
|
||||
if (data.startsWith("minecraft:") || data.contains(":")) {
|
||||
stateData = data;
|
||||
} else if (data.startsWith("[")) {
|
||||
stateData = mat.getKey().toString() + data;
|
||||
} else if (!data.contains("[")) {
|
||||
stateData = mat.getKey().toString();
|
||||
} else {
|
||||
int bracketIdx = data.indexOf('[');
|
||||
stateData = mat.getKey().toString() + data.substring(bracketIdx);
|
||||
}
|
||||
} else {
|
||||
stateData = mat.getKey().toString();
|
||||
}
|
||||
|
||||
return (BlockData) Proxy.newProxyInstance(
|
||||
PatchBukkitBlockData.class.getClassLoader(),
|
||||
new Class<?>[]{BlockData.class},
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("getMaterial".equals(name)) {
|
||||
return mat;
|
||||
}
|
||||
if ("getAsString".equals(name)) {
|
||||
return stateData;
|
||||
}
|
||||
if ("clone".equals(name) || "copy".equals(name)) {
|
||||
return newData(mat, type, stateData);
|
||||
}
|
||||
if ("matches".equals(name) && args != null && args.length == 1) {
|
||||
if (args[0] instanceof BlockData other) {
|
||||
return mat.equals(other.getMaterial());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ("merge".equals(name)) {
|
||||
return proxy;
|
||||
}
|
||||
if ("isSupported".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
if ("isFacingAllowed".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
if ("equals".equals(name) && args != null && args.length == 1) {
|
||||
if (args[0] instanceof BlockData other) {
|
||||
return stateData.equals(other.getAsString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ("hashCode".equals(name)) {
|
||||
return stateData.hashCode();
|
||||
}
|
||||
if ("toString".equals(name)) {
|
||||
return stateData;
|
||||
}
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == boolean.class) return false;
|
||||
if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0;
|
||||
if (returnType == float.class || returnType == double.class) return 0.0f;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,12 +36,10 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
@Override
|
||||
public ComponentFlattener componentFlattener() {
|
||||
return ComponentFlattener.basic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component resolveWithContext(
|
||||
Component component,
|
||||
@Nullable CommandSender context,
|
||||
@ -51,12 +49,25 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createEmptyStack() {
|
||||
return new ItemStack(Material.AIR);
|
||||
private static final java.lang.reflect.Constructor<ItemStack> ITEM_STACK_NO_ARG_CTOR;
|
||||
|
||||
static {
|
||||
try {
|
||||
ITEM_STACK_NO_ARG_CTOR = ItemStack.class.getDeclaredConstructor();
|
||||
ITEM_STACK_NO_ARG_CTOR.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ItemStack createEmptyStack() {
|
||||
try {
|
||||
return ITEM_STACK_NO_ARG_CTOR.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create empty ItemStack", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleEventManager<Plugin> createPluginLifecycleEventManager(
|
||||
JavaPlugin plugin,
|
||||
BooleanSupplier registrationCheck
|
||||
@ -64,52 +75,46 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatisticCriteriaKey(Statistic statistic) {
|
||||
return statistic.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Attributable getDefaultEntityAttributes(NamespacedKey key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDefaultEntityAttributes(NamespacedKey key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack deserializeItem(byte[] bytes) {
|
||||
return new ItemStack(Material.AIR);
|
||||
return createEmptyStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslationKey(EntityType entityType) {
|
||||
return entityType.translationKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.bukkit.entity.SpawnCategory getSpawnCategory(EntityType entityType) {
|
||||
return org.bukkit.entity.SpawnCategory.MISC;
|
||||
}
|
||||
|
||||
public @Nullable DamageEffect getDamageEffect(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PoiType.Occupancy createOccupancy(String type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Pose> validMannequinPoses() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MODERN, LEGACY> GameRule<LEGACY> legacyGameRuleBridge(
|
||||
GameRule<MODERN> modernRule,
|
||||
Function<LEGACY, MODERN> toModern,
|
||||
@ -119,27 +124,22 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component defaultMannequinDescription() {
|
||||
return Component.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinParts.Mutable allSkinParts() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvableProfile defaultMannequinProfile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<CommandSourceStack> restricted(Predicate<CommandSourceStack> predicate) {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombatEntry createCombatEntry(
|
||||
DamageSource damageSource,
|
||||
float damage,
|
||||
@ -149,7 +149,6 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombatEntry createCombatEntry(
|
||||
LivingEntity entity,
|
||||
DamageSource damageSource,
|
||||
@ -158,7 +157,6 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome constructLegacyCustomBiome() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import org.bukkit.plugin.InvalidDescriptionException;
|
||||
@ -32,6 +33,9 @@ public class PatchBukkitPluginClassLoader
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public static final java.util.Set<PatchBukkitPluginClassLoader> ALL_LOADERS =
|
||||
new java.util.concurrent.CopyOnWriteArraySet<>();
|
||||
|
||||
public PatchBukkitPluginClassLoader(ClassLoader parent, File file)
|
||||
throws MalformedURLException, InvalidDescriptionException {
|
||||
this(parent, file, new URL[0]);
|
||||
@ -46,6 +50,194 @@ public class PatchBukkitPluginClassLoader
|
||||
this.file = file;
|
||||
this.description = loadDescription(file);
|
||||
this.dataFolder = new File(file.getParentFile(), description.getName());
|
||||
ALL_LOADERS.add(this);
|
||||
|
||||
File libsDir = new File(file.getParentFile(), "patchbukkit-libs");
|
||||
if (!libsDir.exists()) {
|
||||
libsDir.mkdirs();
|
||||
}
|
||||
|
||||
// Extract and load nested JARs inside plugin JAR
|
||||
for (File nestedJar : extractNestedJars(file, libsDir)) {
|
||||
try {
|
||||
addURL(nestedJar.toURI().toURL());
|
||||
} catch (MalformedURLException ignored) {}
|
||||
}
|
||||
|
||||
List<String> libsToResolve = new ArrayList<>();
|
||||
if (this.description.getLibraries() != null) {
|
||||
libsToResolve.addAll(this.description.getLibraries());
|
||||
}
|
||||
for (String lib : extractLibraries(file)) {
|
||||
if (!libsToResolve.contains(lib)) {
|
||||
libsToResolve.add(lib);
|
||||
}
|
||||
}
|
||||
|
||||
if (!libsToResolve.isEmpty()) {
|
||||
List<File> resolved = LibraryResolver.resolveLibraries(
|
||||
String.join("\n", libsToResolve),
|
||||
libsDir
|
||||
);
|
||||
for (File lib : resolved) {
|
||||
if (lib != null && lib.exists()) {
|
||||
try {
|
||||
addURL(lib.toURI().toURL());
|
||||
} catch (MalformedURLException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> extractNestedJars(File file, File libsDir) {
|
||||
List<File> extractedFiles = new ArrayList<>();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
java.util.Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String name = entry.getName();
|
||||
if (!entry.isDirectory() && name.endsWith(".jar")) {
|
||||
if (name.startsWith("META-INF/jars/") ||
|
||||
name.startsWith("paper-libraries/") ||
|
||||
name.startsWith("BOOT-INF/lib/") ||
|
||||
name.startsWith("lib/") ||
|
||||
name.startsWith("libraries/")) {
|
||||
|
||||
File targetFile = new File(libsDir, new File(name).getName());
|
||||
if (!targetFile.exists()) {
|
||||
try (InputStream is = jar.getInputStream(entry);
|
||||
java.io.FileOutputStream fos = new java.io.FileOutputStream(targetFile)) {
|
||||
is.transferTo(fos);
|
||||
}
|
||||
}
|
||||
if (targetFile.exists()) {
|
||||
extractedFiles.add(targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return extractedFiles;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<String> extractLibraries(File file) {
|
||||
List<String> libraries = new ArrayList<>();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
// 1. Try paper-libraries.json
|
||||
JarEntry entryJson = jar.getJarEntry("paper-libraries.json");
|
||||
if (entryJson != null) {
|
||||
try (InputStream is = jar.getInputStream(entryJson)) {
|
||||
org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml();
|
||||
Object obj = yaml.load(is);
|
||||
if (obj instanceof Map<?, ?> data) {
|
||||
Object repos = data.get("repositories");
|
||||
if (repos instanceof Map<?, ?> repoMap) {
|
||||
for (Object v : repoMap.values()) {
|
||||
if (v != null && !v.toString().isBlank()) {
|
||||
String r = v.toString().trim();
|
||||
if (!libraries.contains("repo:" + r)) {
|
||||
libraries.add("repo:" + r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (repos instanceof List<?> repoList) {
|
||||
for (Object o : repoList) {
|
||||
if (o != null) {
|
||||
String r = o.toString().trim();
|
||||
if (o instanceof Map<?, ?> m && m.containsKey("url")) {
|
||||
r = m.get("url").toString().trim();
|
||||
}
|
||||
if (!libraries.contains("repo:" + r)) {
|
||||
libraries.add("repo:" + r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Object deps = data.get("dependencies");
|
||||
if (deps instanceof List<?> list) {
|
||||
for (Object o : list) {
|
||||
if (o != null && !libraries.contains(o.toString())) {
|
||||
libraries.add(o.toString());
|
||||
}
|
||||
}
|
||||
} else if (deps instanceof Map<?, ?> map) {
|
||||
for (Map.Entry<?, ?> e : map.entrySet()) {
|
||||
if (e.getKey() != null && e.getValue() != null) {
|
||||
String coord = e.getKey().toString() + ":" + e.getValue().toString();
|
||||
if (!libraries.contains(coord)) {
|
||||
libraries.add(coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 2. Try paper-libraries.list
|
||||
JarEntry entryList = jar.getJarEntry("paper-libraries.list");
|
||||
if (entryList != null) {
|
||||
try (InputStream is = jar.getInputStream(entryList);
|
||||
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is, java.nio.charset.StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
if (!libraries.contains(line)) {
|
||||
libraries.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 3. Try paper-plugin.yml or plugin.yml
|
||||
JarEntry entryYaml = jar.getJarEntry("paper-plugin.yml");
|
||||
if (entryYaml == null) {
|
||||
entryYaml = jar.getJarEntry("plugin.yml");
|
||||
}
|
||||
if (entryYaml != null) {
|
||||
try (InputStream is = jar.getInputStream(entryYaml)) {
|
||||
org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml();
|
||||
Object obj = yaml.load(is);
|
||||
if (obj instanceof Map<?, ?> data) {
|
||||
Object libs = data.get("libraries");
|
||||
if (libs instanceof List<?> list) {
|
||||
for (Object o : list) {
|
||||
if (o != null && !libraries.contains(o.toString())) {
|
||||
libraries.add(o.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Object repos = data.get("repositories");
|
||||
if (repos instanceof Map<?, ?> repoMap) {
|
||||
for (Object v : repoMap.values()) {
|
||||
if (v != null && !v.toString().isBlank()) {
|
||||
String r = v.toString().trim();
|
||||
if (!libraries.contains("repo:" + r)) {
|
||||
libraries.add("repo:" + r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (repos instanceof List<?> repoList) {
|
||||
for (Object o : repoList) {
|
||||
if (o != null) {
|
||||
String r = o.toString().trim();
|
||||
if (o instanceof Map<?, ?> m && m.containsKey("url")) {
|
||||
r = m.get("url").toString().trim();
|
||||
}
|
||||
if (!libraries.contains("repo:" + r)) {
|
||||
libraries.add("repo:" + r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private static URL[] buildUrls(File file, URL[] extraUrls)
|
||||
@ -103,10 +295,23 @@ public class PatchBukkitPluginClassLoader
|
||||
!name.startsWith("javax.")
|
||||
) {
|
||||
try {
|
||||
// Try to find in plugin JAR first
|
||||
// Try to find in plugin JAR / libraries first
|
||||
c = findClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Not in JAR, delegate to parent
|
||||
// Not in local JAR, check other plugin classloaders in global pool
|
||||
for (PatchBukkitPluginClassLoader other : ALL_LOADERS) {
|
||||
if (other != this) {
|
||||
try {
|
||||
c = other.findLoadedClass(name);
|
||||
if (c == null) {
|
||||
c = other.findClass(name);
|
||||
}
|
||||
if (c != null) {
|
||||
break;
|
||||
}
|
||||
} catch (ClassNotFoundException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +384,7 @@ public class PatchBukkitPluginClassLoader
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ALL_LOADERS.remove(this);
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,11 +19,12 @@ import org.bukkit.plugin.PluginLoader;
|
||||
import org.bukkit.plugin.RegisteredListener;
|
||||
import org.bukkit.plugin.UnknownDependencyException;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.patchbukkit.PatchBukkitPluginManager;
|
||||
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
|
||||
public static JavaPlugin createPlugin(
|
||||
public static Plugin createPlugin(
|
||||
String jarPath,
|
||||
String mainClass,
|
||||
String extraClasspath,
|
||||
@ -40,7 +41,7 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
|
||||
LinkedHashSet<URL> extraUrls = new LinkedHashSet<>();
|
||||
if (extraClasspath != null && !extraClasspath.isBlank()) {
|
||||
String[] paths = extraClasspath.split(File.pathSeparator);
|
||||
String[] paths = extraClasspath.split("[;:]");
|
||||
for (String path : paths) {
|
||||
if (path == null || path.isBlank()) {
|
||||
continue;
|
||||
@ -76,14 +77,62 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
);
|
||||
|
||||
Class<?> jarClass = Class.forName(mainClass, true, classLoader);
|
||||
return (JavaPlugin) jarClass.getDeclaredConstructor().newInstance();
|
||||
Class<? extends Plugin> pluginClass = jarClass.asSubclass(Plugin.class);
|
||||
Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
|
||||
if (plugin instanceof JavaPlugin javaPlugin) {
|
||||
classLoader.init(javaPlugin);
|
||||
}
|
||||
try {
|
||||
if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) {
|
||||
pm.registerPlugin(plugin);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error during onLoad() for " + mainClass + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
return plugin;
|
||||
} catch (Throwable e) {
|
||||
System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to instantiate plugin " + mainClass, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean enablePlugin(String pluginName) {
|
||||
try {
|
||||
if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) {
|
||||
Plugin p = pm.getPlugin(pluginName);
|
||||
if (p != null) {
|
||||
pm.enablePlugin(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to enable plugin " + pluginName + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean disablePlugin(String pluginName) {
|
||||
try {
|
||||
if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) {
|
||||
Plugin p = pm.getPlugin(pluginName);
|
||||
if (p != null) {
|
||||
pm.disablePlugin(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to disable plugin " + pluginName + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin loadPlugin(File file)
|
||||
throws InvalidPluginException, UnknownDependencyException {
|
||||
@ -106,14 +155,32 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
||||
@Override
|
||||
public void enablePlugin(Plugin plugin) {
|
||||
if (plugin instanceof JavaPlugin javaPlugin) {
|
||||
javaPlugin.setEnabled(true);
|
||||
try {
|
||||
javaPlugin.setEnabled(true);
|
||||
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.server.PluginEnableEvent(plugin));
|
||||
} catch (Throwable ex) {
|
||||
org.bukkit.Bukkit.getLogger().log(
|
||||
java.util.logging.Level.SEVERE,
|
||||
"Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)",
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disablePlugin(Plugin plugin) {
|
||||
if (plugin instanceof JavaPlugin javaPlugin) {
|
||||
javaPlugin.setEnabled(false);
|
||||
try {
|
||||
javaPlugin.setEnabled(false);
|
||||
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.server.PluginDisableEvent(plugin));
|
||||
} catch (Throwable ex) {
|
||||
org.bukkit.Bukkit.getLogger().log(
|
||||
java.util.logging.Level.SEVERE,
|
||||
"Error occurred while disabling " + plugin.getDescription().getFullName(),
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.event.Event;
|
||||
@ -45,9 +46,16 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
throws IllegalArgumentException {}
|
||||
|
||||
public void registerPlugin(@NotNull Plugin plugin) {
|
||||
if (plugin == null || plugin.getName() == null) {
|
||||
return;
|
||||
}
|
||||
plugins.put(plugin.getName().toLowerCase(java.util.Locale.ENGLISH), plugin);
|
||||
for (String provided : plugin.getDescription().getProvides()) {
|
||||
plugins.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), plugin);
|
||||
if (plugin.getDescription() != null && plugin.getDescription().getProvides() != null) {
|
||||
for (String provided : plugin.getDescription().getProvides()) {
|
||||
if (provided != null && !provided.isBlank()) {
|
||||
plugins.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +107,12 @@ 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 {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
server.getLogger().severe("Error loading " + plugin.getName() + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
registerPlugin(plugin);
|
||||
return plugin;
|
||||
} catch (Throwable e) {
|
||||
@ -206,7 +220,7 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
try {
|
||||
plugin.getPluginLoader().enablePlugin(plugin);
|
||||
} catch (Throwable ex) {
|
||||
server.getLogger().severe("Error enabling " + plugin.getName() + ": " + ex.getMessage());
|
||||
server.getLogger().log(Level.SEVERE, "Error enabling " + plugin.getName() + " (Is it up to date?)", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,7 +231,11 @@ public class PatchBukkitPluginManager implements PluginManager {
|
||||
return;
|
||||
}
|
||||
if (plugin.isEnabled()) {
|
||||
plugin.getPluginLoader().disablePlugin(plugin);
|
||||
try {
|
||||
plugin.getPluginLoader().disablePlugin(plugin);
|
||||
} catch (Throwable ex) {
|
||||
server.getLogger().log(Level.SEVERE, "Error disabling " + plugin.getName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandException;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.entity.Entity;
|
||||
@ -105,19 +106,75 @@ import patchbukkit.log.SendLogRequest;
|
||||
|
||||
@SuppressWarnings({ "deprecation", "removal", "unchecked" })
|
||||
public class PatchBukkitServer implements Server {
|
||||
private static volatile PatchBukkitServer INSTANCE;
|
||||
|
||||
private final String serverName =
|
||||
io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
||||
public PatchBukkitServer() {
|
||||
INSTANCE = this;
|
||||
String name = "PatchBukkit";
|
||||
try {
|
||||
name = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
||||
} catch (Throwable ignored) {}
|
||||
this.serverName = name;
|
||||
}
|
||||
|
||||
public static PatchBukkitServer getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (PatchBukkitServer.class) {
|
||||
if (INSTANCE == null) {
|
||||
new PatchBukkitServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> sharedConstants = Class.forName("net.minecraft.SharedConstants");
|
||||
sharedConstants.getMethod("tryDetectVersion").invoke(null);
|
||||
Class<?> bootstrap = Class.forName("net.minecraft.server.Bootstrap");
|
||||
bootstrap.getMethod("bootStrap").invoke(null);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
public static PatchBukkitServer initServer() {
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
||||
logger.log(Level.SEVERE, "Uncaught exception in thread " + thread.getName(), throwable);
|
||||
});
|
||||
PatchBukkitServer server = getInstance();
|
||||
org.bukkit.Bukkit.setServer(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
private final String serverName;
|
||||
private final String bukkitVersion = Versioning.getBukkitVersion();
|
||||
private final CommandMap commandMap = new PatchBukkitCommandMap();
|
||||
private final BukkitScheduler scheduler = new PatchBukkitScheduler();
|
||||
private final PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this);
|
||||
private final ServicesManager servicesManager = new PatchBukkitServicesManager();
|
||||
public SimpleCommandMap commandMap = new PatchBukkitCommandMap(this);
|
||||
public BukkitScheduler scheduler = new PatchBukkitScheduler();
|
||||
public PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this);
|
||||
public ServicesManager servicesManager = new PatchBukkitServicesManager();
|
||||
|
||||
|
||||
private final Map<UUID, Player> onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
private final Map<String, Player> onlinePlayersByName = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
private final Set<UUID> operatorUuids = java.util.Collections.synchronizedSet(new java.util.HashSet<>());
|
||||
private final Set<String> operatorNames = java.util.Collections.synchronizedSet(new java.util.HashSet<>());
|
||||
|
||||
public boolean isOp(UUID uuid, String name) {
|
||||
if (uuid != null && operatorUuids.contains(uuid)) return true;
|
||||
if (name != null && operatorNames.contains(name.toLowerCase())) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setOperator(UUID uuid, String name, boolean value) {
|
||||
if (uuid != null) {
|
||||
if (value) operatorUuids.add(uuid); else operatorUuids.remove(uuid);
|
||||
}
|
||||
if (name != null) {
|
||||
if (value) operatorNames.add(name.toLowerCase()); else operatorNames.remove(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger logger = Logger.getLogger("Minecraft");
|
||||
|
||||
static {
|
||||
@ -144,14 +201,16 @@ public class PatchBukkitServer implements Server {
|
||||
root.addHandler(new java.util.logging.Handler() {
|
||||
@Override
|
||||
public void publish(java.util.logging.LogRecord record) {
|
||||
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
||||
NativeBridgeFfi.sendLog(
|
||||
SendLogRequest.newBuilder()
|
||||
.setLevel(logLevel)
|
||||
.setMessage(record.getMessage())
|
||||
.setLoggerName(record.getLoggerName())
|
||||
.build()
|
||||
);
|
||||
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) {}
|
||||
}
|
||||
@Override public void flush() {}
|
||||
@Override public void close() {}
|
||||
@ -185,6 +244,7 @@ public class PatchBukkitServer implements Server {
|
||||
}
|
||||
|
||||
private final Messenger messenger = new org.patchbukkit.messaging.PatchBukkitMessenger();
|
||||
private final HelpMap helpMap = new org.patchbukkit.help.PatchBukkitHelpMap();
|
||||
private File pluginsFolder = new File("plugins");
|
||||
|
||||
private static void validateChannel(String channel) {
|
||||
@ -590,7 +650,6 @@ public class PatchBukkitServer implements Server {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull World getRespawnWorld() {
|
||||
var worlds = getWorlds();
|
||||
if (!worlds.isEmpty()) {
|
||||
@ -601,7 +660,6 @@ public class PatchBukkitServer implements Server {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRespawnWorld(@NotNull World world) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
@ -904,7 +962,6 @@ public class PatchBukkitServer implements Server {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ServerConfiguration getServerConfig() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
@ -949,28 +1006,23 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public @NotNull OfflinePlayer getOfflinePlayer(@NotNull String name) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getOfflinePlayer'"
|
||||
);
|
||||
Player online = getPlayer(name);
|
||||
if (online != null) return online;
|
||||
return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(null, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable OfflinePlayer getOfflinePlayerIfCached(
|
||||
@NotNull String name
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getOfflinePlayerIfCached'"
|
||||
);
|
||||
public @Nullable OfflinePlayer getOfflinePlayerIfCached(@NotNull String name) {
|
||||
Player online = getPlayer(name);
|
||||
if (online != null) return online;
|
||||
return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(null, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OfflinePlayer getOfflinePlayer(@NotNull UUID id) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getOfflinePlayer'"
|
||||
);
|
||||
Player online = getPlayer(id);
|
||||
if (online != null) return online;
|
||||
return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(id, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1066,10 +1118,14 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public @NotNull Set<OfflinePlayer> getOperators() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getOperators'"
|
||||
);
|
||||
Set<OfflinePlayer> ops = new java.util.HashSet<>();
|
||||
for (UUID uuid : operatorUuids) {
|
||||
ops.add(getOfflinePlayer(uuid));
|
||||
}
|
||||
for (String name : operatorNames) {
|
||||
ops.add(getOfflinePlayer(name));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1088,7 +1144,6 @@ public class PatchBukkitServer implements Server {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forcesDefaultGameMode() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
@ -1134,10 +1189,7 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public @NotNull HelpMap getHelpMap() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getHelpMap'"
|
||||
);
|
||||
return this.helpMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1252,10 +1304,7 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryThread() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'isPrimaryThread'"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1317,10 +1366,7 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public @NotNull ItemFactory getItemFactory() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getItemFactory'"
|
||||
);
|
||||
return org.patchbukkit.inventory.PatchBukkitItemFactory.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1475,18 +1521,12 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public double@NotNull [] getTPS() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getTPS'"
|
||||
);
|
||||
return new double[] { 20.0, 20.0, 20.0 };
|
||||
}
|
||||
|
||||
@Override
|
||||
public long@NotNull [] getTickTimes() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getTickTimes'"
|
||||
);
|
||||
return new long[100];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1554,33 +1594,96 @@ public class PatchBukkitServer implements Server {
|
||||
BlockType type = null;
|
||||
if (material != null) {
|
||||
type = material.asBlockType();
|
||||
if (type == null && material.isBlock()) {
|
||||
type = org.patchbukkit.registry.PatchBukkitBlockType.create(material);
|
||||
}
|
||||
Preconditions.checkArgument(type != null, "Provided material must be a block");
|
||||
} else if (data != null) {
|
||||
String matName = data.trim();
|
||||
int stateIndex = matName.indexOf('[');
|
||||
if (stateIndex != -1) {
|
||||
matName = matName.substring(0, stateIndex).trim();
|
||||
}
|
||||
if (matName.startsWith("minecraft:")) {
|
||||
matName = matName.substring("minecraft:".length());
|
||||
}
|
||||
Material matched = null;
|
||||
try {
|
||||
matched = Material.valueOf(matName.toUpperCase(java.util.Locale.ROOT));
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
if (matched == null || matched.isLegacy()) {
|
||||
Material m = Material.matchMaterial(matName, false);
|
||||
if (m != null && !m.isLegacy()) {
|
||||
matched = m;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched == null) {
|
||||
matched = Material.matchMaterial(matName);
|
||||
}
|
||||
|
||||
if (matched != null && matched.isLegacy()) {
|
||||
matched = PatchBukkitLegacy.fromLegacy(matched);
|
||||
}
|
||||
|
||||
if (matched != null && (matched.isBlock() || (!matched.isLegacy() && matched.getKey() != null))) {
|
||||
material = matched;
|
||||
type = matched.asBlockType();
|
||||
if (type == null) {
|
||||
type = org.patchbukkit.registry.PatchBukkitBlockType.create(matched);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Block name " + matName + " was not recognized");
|
||||
}
|
||||
}
|
||||
|
||||
return PatchBukkitBlockData.newData(type, data);
|
||||
return PatchBukkitBlockData.newData(material, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Keyed> @Nullable Tag<T> getTag(
|
||||
@NotNull String registry,
|
||||
@NotNull NamespacedKey tag,
|
||||
@NotNull Class<T> clazz
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getTag'"
|
||||
);
|
||||
if (registry == null || tag == null || clazz == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
for (java.lang.reflect.Field field : org.bukkit.Tag.class.getFields()) {
|
||||
if (Tag.class.isAssignableFrom(field.getType())) {
|
||||
Tag<?> val = (Tag<?>) field.get(null);
|
||||
if (val != null && val.getKey().equals(tag)) {
|
||||
return (Tag<T>) val;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return new org.patchbukkit.tag.PatchBukkitTag<>(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Keyed> @NotNull Iterable<Tag<T>> getTags(
|
||||
@NotNull String registry,
|
||||
@NotNull Class<T> clazz
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getTags'"
|
||||
);
|
||||
List<Tag<T>> result = new ArrayList<>();
|
||||
try {
|
||||
for (java.lang.reflect.Field field : org.bukkit.Tag.class.getFields()) {
|
||||
if (Tag.class.isAssignableFrom(field.getType())) {
|
||||
Tag<?> val = (Tag<?>) field.get(null);
|
||||
if (val != null) {
|
||||
result.add((Tag<T>) val);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1719,18 +1822,12 @@ public class PatchBukkitServer implements Server {
|
||||
|
||||
@Override
|
||||
public int getCurrentTick() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getCurrentTick'"
|
||||
);
|
||||
return (int) (System.currentTimeMillis() / 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopping() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'isStopping'"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1894,9 +1991,28 @@ public class PatchBukkitServer implements Server {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Path getLevelDirectory() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLevelDirectory'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAmbientSpawnLimit() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWaterUndergroundCreatureSpawnLimit() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWaterAmbientSpawnLimit() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWaterAnimalSpawnLimit() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,18 @@ public class PatchBukkitUnsafeValues implements UnsafeValues {
|
||||
public static final PatchBukkitUnsafeValues INSTANCE =
|
||||
new PatchBukkitUnsafeValues();
|
||||
|
||||
public @NotNull List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull TooltipContext tooltipContext, @Nullable Player player) {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
|
||||
public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> createPluginLifecycleEventManager(org.bukkit.plugin.java.JavaPlugin plugin, java.util.function.BooleanSupplier registrationCheck) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable Color getSpawnEggLayerColor(EntityType entityType, int layer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedApiVersion(String apiVersion) {
|
||||
if (apiVersion == null) return false;
|
||||
@ -78,8 +90,7 @@ public class PatchBukkitUnsafeValues implements UnsafeValues {
|
||||
|
||||
@Override
|
||||
public Material toLegacy(Material material) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'toLegacy'");
|
||||
return material;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -99,122 +110,107 @@ public class PatchBukkitUnsafeValues implements UnsafeValues {
|
||||
|
||||
@Override
|
||||
public BlockData fromLegacy(Material material, byte data) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'fromLegacy'");
|
||||
return material != null ? material.createBlockData() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material getMaterial(String material, int version) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMaterial'");
|
||||
try {
|
||||
return Material.valueOf(material.toUpperCase(java.util.Locale.ROOT));
|
||||
} catch (Exception e) {
|
||||
return Material.getMaterial(material);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataVersion() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getDataVersion'");
|
||||
return 3953;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack modifyItemStack(ItemStack stack, String arguments) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'modifyItemStack'");
|
||||
return stack;
|
||||
}
|
||||
|
||||
public @NotNull ItemStack createEmptyStack() {
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'processClass'");
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advancement loadAdvancement(NamespacedKey key, String advancement) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'loadAdvancement'");
|
||||
public Advancement loadAdvancement(net.kyori.adventure.key.Key key, String advancement, boolean checkKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Advancement> loadAdvancements(Map<net.kyori.adventure.key.Key, String> advancements, boolean checkKey) {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAdvancement(NamespacedKey key) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'removeAdvancement'");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(Class<?> aClass, String value) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'get'");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends Keyed> B get(RegistryKey<B> registry, NamespacedKey key) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'get'");
|
||||
if (registry == null || key == null) return null;
|
||||
org.bukkit.Registry<B> reg = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registry);
|
||||
return reg != null ? reg.get(key) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonObject serializeItemAsJson(@NotNull ItemStack itemStack) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'serializeItemAsJson'");
|
||||
return new JsonObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack deserializeItemFromJson(@NotNull JsonObject data) throws IllegalArgumentException {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'deserializeItemFromJson'");
|
||||
return new ItemStack(Material.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serializeEntity(@NotNull Entity entity,
|
||||
@NotNull EntitySerializationFlag... serializationFlags) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'serializeEntity'");
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entity deserializeEntity(byte @NotNull [] data, @NotNull World world, boolean preserveUUID,
|
||||
boolean preservePassengers) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'deserializeEntity'");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @NotNull String getMainLevelName() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMainLevelName'");
|
||||
return "world";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getProtocolVersion'");
|
||||
return 769;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack deserializeStack(@NotNull Map<String, Object> args) {
|
||||
System.out.println("Deserializing itemstack: " + args);
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'deserializeStack'");
|
||||
return new ItemStack(Material.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack deserializeItemHover(@NotNull ShowItem itemHover) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'deserializeItemHover'");
|
||||
return new ItemStack(Material.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalPotionData getInternalPotionData(NamespacedKey key) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getInternalPotionData'");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextEntityId(World world) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'nextEntityId'");
|
||||
return (int) (System.currentTimeMillis() & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -12,17 +12,32 @@ public class BridgeUtils {
|
||||
}
|
||||
|
||||
public static patchbukkit.common.Location convertLocation(org.bukkit.Location location) {
|
||||
if (location == null) return null;
|
||||
var pos = patchbukkit.common.Vec3.newBuilder()
|
||||
.setX(location.getX())
|
||||
.setY(location.getY())
|
||||
.setZ(location.getZ())
|
||||
.build();
|
||||
var worldUuid = convertUuid(location.getWorld().getUID());
|
||||
var worldUuid = location.getWorld() != null ? convertUuid(location.getWorld().getUID()) : null;
|
||||
var worldBuilder = patchbukkit.common.World.newBuilder();
|
||||
if (worldUuid != null) worldBuilder.setUuid(worldUuid);
|
||||
return patchbukkit.common.Location.newBuilder()
|
||||
.setWorld(patchbukkit.common.World.newBuilder().setUuid(worldUuid).build())
|
||||
.setWorld(worldBuilder.build())
|
||||
.setPosition(pos)
|
||||
.setYaw(location.getYaw())
|
||||
.setPitch(location.getPitch())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static org.bukkit.Location convertLocation(patchbukkit.common.Location location) {
|
||||
if (location == null) return null;
|
||||
org.bukkit.World world = null;
|
||||
if (location.hasWorld() && location.getWorld().hasUuid()) {
|
||||
world = org.patchbukkit.world.PatchBukkitWorld.getOrCreate(convertUuid(location.getWorld().getUuid()));
|
||||
}
|
||||
double x = location.hasPosition() ? location.getPosition().getX() : 0.0;
|
||||
double y = location.hasPosition() ? location.getPosition().getY() : 0.0;
|
||||
double z = location.hasPosition() ? location.getPosition().getZ() : 0.0;
|
||||
return new org.bukkit.Location(world, x, y, z, location.getYaw(), location.getPitch());
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,4 +15,31 @@ public class CommandFactory {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PluginCommand create(String name, String pluginName) {
|
||||
try {
|
||||
Plugin plugin = null;
|
||||
if (org.bukkit.Bukkit.getPluginManager() instanceof org.patchbukkit.PatchBukkitPluginManager pm) {
|
||||
plugin = pm.getPlugin(pluginName);
|
||||
if (plugin == null) {
|
||||
for (Plugin p : pm.getPlugins()) {
|
||||
if (p.getName().equalsIgnoreCase(pluginName)) {
|
||||
plugin = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plugin == null) {
|
||||
plugin = org.bukkit.Bukkit.getPluginManager().getPlugin(pluginName);
|
||||
}
|
||||
if (plugin == null) {
|
||||
System.err.println("[PatchBukkit] CommandFactory: Plugin not found by name '" + pluginName + "' for command '" + name + "'");
|
||||
}
|
||||
return create(name, plugin);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,136 +16,108 @@ import org.jetbrains.annotations.Nullable;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public class PatchBukkitBlockCommandSender implements BlockCommandSender {
|
||||
@NotNull
|
||||
@Override
|
||||
public Block getBlock() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getBlock'");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermissionSet(@NotNull String name) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermissionSet(@NotNull Permission perm) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String name) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'hasPermission'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull Permission perm) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'hasPermission'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
||||
return new PermissionAttachment(plugin, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
||||
return new PermissionAttachment(plugin, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value,
|
||||
int ticks) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
||||
return new PermissionAttachment(plugin, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
||||
return new PermissionAttachment(plugin, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttachment(@NotNull PermissionAttachment attachment) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'removeAttachment'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recalculatePermissions() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'recalculatePermissions'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getEffectivePermissions'");
|
||||
return java.util.Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isOp'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOp(boolean value) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setOp'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull String message) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull String... messages) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@Nullable UUID sender, @NotNull String message) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@Nullable UUID sender, @NotNull String... messages) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Server getServer() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getServer'");
|
||||
return org.bukkit.Bukkit.getServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getName'");
|
||||
return "@";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Spigot spigot() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'spigot'");
|
||||
return new Spigot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component name() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'name'");
|
||||
return Component.text("@");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,16 +7,56 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandException;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
import patchbukkit.command.RegisterCommandRequest;
|
||||
|
||||
public class PatchBukkitCommandMap implements CommandMap {
|
||||
// We use Command as the value to support both PluginCommand and vanilla commands
|
||||
private final Map<String, Command> knownCommands = new HashMap<>();
|
||||
public class PatchBukkitCommandMap extends SimpleCommandMap {
|
||||
|
||||
public PatchBukkitCommandMap(Server server) {
|
||||
super(server != null ? server : org.bukkit.Bukkit.getServer(), new HashMap<>());
|
||||
}
|
||||
|
||||
public PatchBukkitCommandMap() {
|
||||
super(org.bukkit.Bukkit.getServer(), new HashMap<>());
|
||||
}
|
||||
|
||||
private String cleanLabel(String label) {
|
||||
if (label == null) return "";
|
||||
String clean = label.trim();
|
||||
while (clean.startsWith("/")) {
|
||||
clean = clean.substring(1).trim();
|
||||
}
|
||||
return clean.toLowerCase();
|
||||
}
|
||||
|
||||
private void registerVariants(String key, Command command) {
|
||||
if (key == null || key.isEmpty()) return;
|
||||
key = key.toLowerCase().trim();
|
||||
if (!knownCommands.containsKey(key)) {
|
||||
knownCommands.put(key, command);
|
||||
}
|
||||
String clean = cleanLabel(key);
|
||||
if (!clean.isEmpty()) {
|
||||
if (!knownCommands.containsKey(clean)) {
|
||||
knownCommands.put(clean, command);
|
||||
}
|
||||
String single = "/" + clean;
|
||||
if (!knownCommands.containsKey(single)) {
|
||||
knownCommands.put(single, command);
|
||||
}
|
||||
String doubleSlash = "//" + clean;
|
||||
if (!knownCommands.containsKey(doubleSlash)) {
|
||||
knownCommands.put(doubleSlash, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAll(@NotNull String fallbackPrefix, @NotNull List<Command> commands) {
|
||||
@ -27,26 +67,45 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
|
||||
@Override
|
||||
public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) {
|
||||
if (label == null || fallbackPrefix == null || command == null) {
|
||||
return false;
|
||||
}
|
||||
label = label.toLowerCase().trim();
|
||||
fallbackPrefix = fallbackPrefix.toLowerCase().trim();
|
||||
|
||||
// 1. Register under the direct label if not taken
|
||||
String clean = cleanLabel(label);
|
||||
|
||||
boolean registeredDirectly = false;
|
||||
if (!knownCommands.containsKey(label)) {
|
||||
knownCommands.put(label, command);
|
||||
if (!knownCommands.containsKey(label) && !knownCommands.containsKey(clean)) {
|
||||
registeredDirectly = true;
|
||||
}
|
||||
|
||||
// 2. Always register under the fallback prefix (pluginname:label)
|
||||
knownCommands.put(fallbackPrefix + ":" + label, command);
|
||||
registerVariants(label, command);
|
||||
registerVariants(fallbackPrefix + ":" + label, command);
|
||||
if (!clean.isEmpty()) {
|
||||
registerVariants(clean, command);
|
||||
registerVariants(fallbackPrefix + ":" + clean, command);
|
||||
}
|
||||
|
||||
// 3. Register aliases
|
||||
for (String alias : command.getAliases()) {
|
||||
alias = alias.toLowerCase().trim();
|
||||
if (!knownCommands.containsKey(alias)) {
|
||||
knownCommands.put(alias, command);
|
||||
if (command.getAliases() != null) {
|
||||
for (String alias : command.getAliases()) {
|
||||
if (alias == null) continue;
|
||||
alias = alias.toLowerCase().trim();
|
||||
registerVariants(alias, command);
|
||||
registerVariants(fallbackPrefix + ":" + alias, command);
|
||||
}
|
||||
knownCommands.put(fallbackPrefix + ":" + alias, command);
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> aliasesList = command.getAliases() != null ? command.getAliases() : Collections.emptyList();
|
||||
String desc = command.getDescription() != null ? command.getDescription() : "";
|
||||
RegisterCommandRequest request = RegisterCommandRequest.newBuilder()
|
||||
.setCmdName(label)
|
||||
.addAllAliases(aliasesList)
|
||||
.setDescription(desc)
|
||||
.setPluginName(fallbackPrefix)
|
||||
.build();
|
||||
NativeBridgeFfi.registerCommand(request);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return registeredDirectly;
|
||||
@ -54,32 +113,34 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
|
||||
@Override
|
||||
public boolean register(@NotNull String fallbackPrefix, @NotNull Command command) {
|
||||
if (command == null) return false;
|
||||
return register(command.getName(), fallbackPrefix, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException {
|
||||
if (cmdLine == null) return false;
|
||||
String cleanLine = cmdLine.trim();
|
||||
while (cleanLine.startsWith("/")) {
|
||||
cleanLine = cleanLine.substring(1).trim();
|
||||
}
|
||||
if (cleanLine.isEmpty()) return false;
|
||||
String rawLine = cmdLine.trim();
|
||||
if (rawLine.isEmpty()) return false;
|
||||
|
||||
String[] split = cleanLine.split("\\s+");
|
||||
String[] split = rawLine.split("\\s+");
|
||||
if (split.length == 0) return false;
|
||||
|
||||
String sentLabel = split[0].toLowerCase();
|
||||
Command command = knownCommands.get(sentLabel);
|
||||
String rawLabel = split[0].toLowerCase();
|
||||
Command command = knownCommands.get(rawLabel);
|
||||
|
||||
if (command == null) {
|
||||
String clean = cleanLabel(rawLabel);
|
||||
command = knownCommands.get(clean);
|
||||
}
|
||||
|
||||
if (command == null) {
|
||||
return false; // Command not found
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract arguments (everything after the label)
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
return command.execute(sender, sentLabel, args);
|
||||
return command.execute(sender, rawLabel, args);
|
||||
} catch (Exception ex) {
|
||||
throw new CommandException("Unhandled exception executing '" + cmdLine + "'", ex);
|
||||
}
|
||||
@ -92,7 +153,13 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
|
||||
@Override
|
||||
public @Nullable Command getCommand(@NotNull String name) {
|
||||
return knownCommands.get(name.toLowerCase());
|
||||
if (name == null) return null;
|
||||
String lower = name.toLowerCase().trim();
|
||||
Command cmd = knownCommands.get(lower);
|
||||
if (cmd == null) {
|
||||
cmd = knownCommands.get(cleanLabel(lower));
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,17 +175,16 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
@Override
|
||||
public @Nullable List<String> tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) {
|
||||
if (cmdLine == null) return new ArrayList<>();
|
||||
String cleanLine = cmdLine.trim();
|
||||
while (cleanLine.startsWith("/")) {
|
||||
cleanLine = cleanLine.substring(1).trim();
|
||||
}
|
||||
String[] split = cleanLine.split(" ", -1);
|
||||
String rawLine = cmdLine.trim();
|
||||
String[] split = rawLine.split(" ", -1);
|
||||
if (split.length == 0) return new ArrayList<>();
|
||||
|
||||
String label = split[0].toLowerCase();
|
||||
Command command = knownCommands.get(label);
|
||||
if (command == null) {
|
||||
command = knownCommands.get(cleanLabel(label));
|
||||
}
|
||||
|
||||
// If typing the first word, return matching command names
|
||||
if (split.length == 1) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
for (String key : knownCommands.keySet()) {
|
||||
@ -127,7 +193,6 @@ public class PatchBukkitCommandMap implements CommandMap {
|
||||
return completions;
|
||||
}
|
||||
|
||||
// Otherwise, delegate to the specific command's tab completer
|
||||
if (command != null) {
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
return command.tabComplete(sender, label, args, location);
|
||||
|
||||
@ -28,6 +28,7 @@ import org.bukkit.entity.Pose;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityRemoveEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
@ -59,6 +60,16 @@ import patchbukkit.entity.TeleportEntityRequest;
|
||||
|
||||
public class PatchBukkitEntity implements Entity {
|
||||
|
||||
@Override
|
||||
public boolean isInWaterOrRainOrBubbleColumn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInWaterOrBubbleColumn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected final UUID uuid;
|
||||
protected final String name;
|
||||
private PermissibleBase perm;
|
||||
@ -233,13 +244,11 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getPersistentDataContainer'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @org.jspecify.annotations.Nullable T getData(Valued<T> type) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getData'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @org.jspecify.annotations.Nullable T getDataOrDefault(Valued<? extends T> type,
|
||||
@org.jspecify.annotations.Nullable T fallback) {
|
||||
// TODO Auto-generated method stub
|
||||
@ -329,8 +338,8 @@ public class PatchBukkitEntity implements Entity {
|
||||
|
||||
@Override
|
||||
public boolean isOnGround() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isOnGround'");
|
||||
var resp = NativeBridgeFfi.isOnGround(BridgeUtils.convertUuid(this.uuid));
|
||||
return resp != null && resp.getOnGround();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -435,7 +444,6 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setVisualFire'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisualFire() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isVisualFire'");
|
||||
@ -584,22 +592,21 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'eject'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getPickItemStack() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getPickItemStack'");
|
||||
}
|
||||
|
||||
private float fallDistance = 0.0f;
|
||||
|
||||
@Override
|
||||
public float getFallDistance() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getFallDistance'");
|
||||
return this.fallDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFallDistance(float distance) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setFallDistance'");
|
||||
this.fallDistance = distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -701,7 +708,6 @@ public class PatchBukkitEntity implements Entity {
|
||||
return this.visibleByDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Player> getTrackedBy() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getTrackedBy'");
|
||||
@ -809,16 +815,16 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getPose'");
|
||||
}
|
||||
|
||||
private boolean sneaking = false;
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isSneaking'");
|
||||
return this.sneaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSneaking(boolean sneak) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setSneaking'");
|
||||
this.sneaking = sneak;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -827,12 +833,20 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setPose'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFixedPose() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'hasFixedPose'");
|
||||
}
|
||||
|
||||
public EntityRemoveEvent.@Nullable Cause getRemoveEventCause() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.papermc.paper.entity.@Nullable RemovalReason getRemovalReason() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull SpawnCategory getSpawnCategory() {
|
||||
// TODO Auto-generated method stub
|
||||
@ -995,13 +1009,11 @@ public class PatchBukkitEntity implements Entity {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getScoreboardEntryName'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastHurtAnimation(@NotNull Collection<Player> players) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'broadcastHurtAnimation'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Source soundSource() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'soundSource'");
|
||||
|
||||
@ -50,6 +50,7 @@ import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
import org.patchbukkit.PatchBukkitServer;
|
||||
import org.patchbukkit.bridge.BridgeUtils;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
|
||||
@ -67,20 +68,67 @@ public class PatchBukkitHumanEntity
|
||||
implements HumanEntity {
|
||||
private boolean op;
|
||||
protected final PermissibleBase perm = new PermissibleBase(this);
|
||||
protected final PlayerInventory inventory = new org.patchbukkit.inventory.PatchBukkitPlayerInventory(this);
|
||||
protected final EntityEquipment equipment = new org.patchbukkit.inventory.PatchBukkitEntityEquipment(this);
|
||||
private ItemStack cursorItem = ItemStack.empty();
|
||||
|
||||
|
||||
public PatchBukkitHumanEntity(UUID uuid,
|
||||
String name) {
|
||||
super(uuid, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean undiscoverRecipe(@NotNull NamespacedKey key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int undiscoverRecipes(@NotNull Collection<NamespacedKey> keys) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean discoverRecipe(@NotNull NamespacedKey key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int discoverRecipes(@NotNull Collection<NamespacedKey> keys) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
return this.op;
|
||||
if (this.op) {
|
||||
return true;
|
||||
}
|
||||
if (PatchBukkitServer.getInstance().isOp(getUniqueId(), getName())) {
|
||||
this.op = true;
|
||||
if (this.perm != null) {
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
var resp = NativeBridgeFfi.isOp(BridgeUtils.convertUuid(getUniqueId()));
|
||||
if (resp != null && resp.getIsOp()) {
|
||||
this.op = true;
|
||||
if (this.perm != null) {
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOp(boolean value) {
|
||||
this.op = value;
|
||||
PatchBukkitServer.getInstance().setOperator(getUniqueId(), getName(), value);
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
|
||||
@ -104,23 +152,7 @@ public class PatchBukkitHumanEntity
|
||||
return this.perm.hasPermission(perm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getEyeHeight() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getEyeHeight(boolean ignorePose) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Location getEyeLocation() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getEyeLocation'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<Block> getLineOfSight(@Nullable Set<Material> transparent, int maxDistance) {
|
||||
@ -794,71 +826,7 @@ public class PatchBukkitHumanEntity
|
||||
throw new UnsupportedOperationException("Unimplemented method 'registerAttribute'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(double amount) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'damage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(double amount, @Nullable Entity source) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'damage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(double amount, @NotNull DamageSource damageSource) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'damage'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getHealth'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealth(double health) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setHealth'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heal(double amount, @NotNull RegainReason reason) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'heal'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAbsorptionAmount() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getAbsorptionAmount'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAbsorptionAmount(double amount) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setAbsorptionAmount'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxHealth() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMaxHealth'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxHealth(double health) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setMaxHealth'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMaxHealth() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'resetMaxHealth'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Projectile> @NotNull T launchProjectile(@NotNull Class<? extends T> projectile,
|
||||
@ -881,14 +849,12 @@ public class PatchBukkitHumanEntity
|
||||
|
||||
@Override
|
||||
public EntityEquipment getEquipment() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getEquipment'");
|
||||
return this.equipment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerInventory getInventory() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getInventory'");
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -899,10 +865,10 @@ public class PatchBukkitHumanEntity
|
||||
|
||||
@Override
|
||||
public MainHand getMainHand() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMainHand'");
|
||||
return MainHand.RIGHT;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setWindowProperty(Property prop, int value) {
|
||||
// TODO Auto-generated method stub
|
||||
@ -1009,28 +975,26 @@ public class PatchBukkitHumanEntity
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInHand() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getItemInHand'");
|
||||
return getInventory().getItemInMainHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInHand(@org.jspecify.annotations.Nullable ItemStack item) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setItemInHand'");
|
||||
getInventory().setItemInMainHand(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemOnCursor() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getItemOnCursor'");
|
||||
return this.cursorItem != null ? this.cursorItem : ItemStack.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemOnCursor(@org.jspecify.annotations.Nullable ItemStack item) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setItemOnCursor'");
|
||||
this.cursorItem = item != null ? item : ItemStack.empty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasCooldown(Material material) {
|
||||
// TODO Auto-generated method stub
|
||||
@ -1061,25 +1025,21 @@ public class PatchBukkitHumanEntity
|
||||
throw new UnsupportedOperationException("Unimplemented method 'hasCooldown'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCooldown(ItemStack item) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCooldown'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCooldown(ItemStack item, int ticks) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setCooldown'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCooldown(Key key) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCooldown'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCooldown(Key key, int ticks) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setCooldown'");
|
||||
@ -1183,17 +1143,7 @@ public class PatchBukkitHumanEntity
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getAttackCooldown'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int discoverRecipes(Collection<NamespacedKey> recipes) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'discoverRecipes'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int undiscoverRecipes(Collection<NamespacedKey> recipes) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'undiscoverRecipes'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDiscoveredRecipe(NamespacedKey recipe) {
|
||||
|
||||
@ -852,51 +852,55 @@ public class PatchBukkitLivingEntity
|
||||
throw new UnsupportedOperationException("Unimplemented method 'canUseEquipmentSlot'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CombatTracker getCombatTracker() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCombatTracker'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShieldBlockingDelay(int delay) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShieldBlockingDelay() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setWaypointStyle(@Nullable Key key) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setWaypointStyle'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWaypointColor(@Nullable Color color) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setWaypointColor'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Key getWaypointStyle() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getWaypointStyle'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Color getWaypointColor() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getWaypointColor'");
|
||||
}
|
||||
@Override
|
||||
|
||||
public void kill(DamageSource damageSource) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'kill'");
|
||||
}
|
||||
@Override
|
||||
|
||||
public float getSoundVolume() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getSoundVolume'");
|
||||
}
|
||||
@Override
|
||||
|
||||
public float getSoundPitch() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getSoundPitch'");
|
||||
}
|
||||
@Override
|
||||
|
||||
public @Nullable Sound getHurtSound(@NotNull DamageSource damageSource) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getHurtSound'");
|
||||
|
||||
@ -0,0 +1,214 @@
|
||||
package org.patchbukkit.entity;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.patchbukkit.PatchBukkitServer;
|
||||
|
||||
public class PatchBukkitOfflinePlayer implements OfflinePlayer {
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
|
||||
public PatchBukkitOfflinePlayer(UUID uuid, String name) {
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getName() {
|
||||
Player player = getPlayer();
|
||||
return player != null ? player.getName() : this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UUID getUniqueId() {
|
||||
if (this.uuid != null) return this.uuid;
|
||||
Player player = getPlayer();
|
||||
if (player != null) return player.getUniqueId();
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + (this.name != null ? this.name : "")).getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
Player player = getPlayer();
|
||||
if (player != null) return player.isOp();
|
||||
return PatchBukkitServer.getInstance().isOp(getUniqueId(), getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOp(boolean value) {
|
||||
PatchBukkitServer.getInstance().setOperator(getUniqueId(), getName(), value);
|
||||
Player player = getPlayer();
|
||||
if (player != null) {
|
||||
player.setOp(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBanned() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends org.bukkit.BanEntry<? super com.destroystokyo.paper.profile.PlayerProfile>> E ban(String reason, java.util.Date expires, String source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends org.bukkit.BanEntry<? super com.destroystokyo.paper.profile.PlayerProfile>> E ban(String reason, java.time.Instant expires, String source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends org.bukkit.BanEntry<? super com.destroystokyo.paper.profile.PlayerProfile>> E ban(String reason, java.time.Duration duration, String source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWhitelisted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWhitelisted(boolean value) {}
|
||||
|
||||
@Override
|
||||
public @Nullable Player getPlayer() {
|
||||
if (this.uuid != null) {
|
||||
Player p = PatchBukkitServer.getInstance().getPlayer(this.uuid);
|
||||
if (p != null) return p;
|
||||
}
|
||||
if (this.name != null) {
|
||||
return PatchBukkitServer.getInstance().getPlayer(this.name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFirstPlayed() { return 0; }
|
||||
|
||||
@Override
|
||||
public long getLastPlayed() { return 0; }
|
||||
|
||||
@Override
|
||||
public boolean hasPlayedBefore() { return true; }
|
||||
|
||||
@Override
|
||||
public @Nullable Location getLocation() {
|
||||
Player player = getPlayer();
|
||||
return player != null ? player.getLocation() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Location getBedSpawnLocation() { return null; }
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic) {}
|
||||
|
||||
@Override
|
||||
public int getStatistic(@NotNull Statistic statistic) { return 0; }
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic, int amount) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic, int amount) {}
|
||||
|
||||
@Override
|
||||
public void setStatistic(@NotNull Statistic statistic, int newValue) {}
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) {}
|
||||
|
||||
@Override
|
||||
public int getStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) { return 0; }
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int amount) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int amount) {}
|
||||
|
||||
@Override
|
||||
public void setStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int newValue) {}
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) {}
|
||||
|
||||
@Override
|
||||
public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) { return 0; }
|
||||
|
||||
@Override
|
||||
public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {}
|
||||
|
||||
@Override
|
||||
public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {}
|
||||
|
||||
@Override
|
||||
public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) {}
|
||||
|
||||
@Override
|
||||
public @Nullable Location getLastDeathLocation() {
|
||||
Player player = getPlayer();
|
||||
return player != null ? player.getLastDeathLocation() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Location getRespawnLocation() {
|
||||
Player player = getPlayer();
|
||||
return player != null ? player.getBedSpawnLocation() : null;
|
||||
}
|
||||
|
||||
public @Nullable Location getRespawnLocation(boolean anchor) {
|
||||
Player player = getPlayer();
|
||||
return player != null ? player.getBedSpawnLocation() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.destroystokyo.paper.profile.@NotNull PlayerProfile getPlayerProfile() {
|
||||
Player player = getPlayer();
|
||||
if (player != null) return player.getPlayerProfile();
|
||||
return org.bukkit.Bukkit.getServer().createProfile(getUniqueId(), getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastLogin() { return getLastPlayed(); }
|
||||
|
||||
@Override
|
||||
public long getLastSeen() { return getLastPlayed(); }
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() {
|
||||
return new org.patchbukkit.persistence.PatchBukkitPersistentDataContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<String, Object> serialize() {
|
||||
return Map.of("uuid", getUniqueId().toString(), "name", getName() != null ? getName() : "");
|
||||
}
|
||||
}
|
||||
@ -83,10 +83,32 @@ public class PatchBukkitPlayer
|
||||
|
||||
private final Map<UUID, Set<WeakReference<Plugin>>> invertedVisibilityEntities = new HashMap<>();
|
||||
|
||||
public PatchBukkitPlayer(UUID uuid, String name) {
|
||||
public PatchBukkitPlayer(UUID uuid, String name) {
|
||||
super(uuid, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openSign(@NotNull org.bukkit.block.Sign sign) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data, boolean force) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data) {
|
||||
}
|
||||
|
||||
public void unsetFixedPose() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'unsetFixedPose'");
|
||||
}
|
||||
|
||||
public void resetFlyingTicks() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'resetFlyingTicks'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRawMessage(String message) {
|
||||
this.sendRawMessage(null, message);
|
||||
@ -247,7 +269,6 @@ public class PatchBukkitPlayer
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLastSeen'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @org.jspecify.annotations.Nullable Location getRespawnLocation(boolean loadLocationAndValidate) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getRespawnLocation'");
|
||||
@ -620,16 +641,16 @@ public class PatchBukkitPlayer
|
||||
throw new UnsupportedOperationException("Unimplemented method 'performCommand'");
|
||||
}
|
||||
|
||||
private boolean sprinting = false;
|
||||
|
||||
@Override
|
||||
public boolean isSprinting() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isSprinting'");
|
||||
return this.sprinting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSprinting(boolean sprinting) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setSprinting'");
|
||||
this.sprinting = sprinting;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -680,6 +701,16 @@ public class PatchBukkitPlayer
|
||||
throw new UnsupportedOperationException("Unimplemented method 'playNote'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(Location location, Sound sound, float volume, float pitch) {
|
||||
playSound(location, sound, SoundCategory.MASTER, volume, pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(Location location, String sound, float volume, float pitch) {
|
||||
playSound(location, sound, SoundCategory.MASTER, volume, pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) {
|
||||
var patchBukkitSound = (PatchBukkitSound) sound;
|
||||
@ -1485,8 +1516,16 @@ public class PatchBukkitPlayer
|
||||
@Override
|
||||
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX,
|
||||
double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data, boolean force) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'spawnParticle'");
|
||||
if (particle == null) return;
|
||||
var request = patchbukkit.world.SpawnParticleRequest.newBuilder()
|
||||
.setWorldUuid(BridgeUtils.convertUuid(this.getWorld().getUID()))
|
||||
.setParticle(particle.name().toLowerCase())
|
||||
.setX(x).setY(y).setZ(z)
|
||||
.setCount(count)
|
||||
.setOffsetX(offsetX).setOffsetY(offsetY).setOffsetZ(offsetZ)
|
||||
.setExtra(extra)
|
||||
.build();
|
||||
NativeBridgeFfi.spawnParticle(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1503,8 +1542,16 @@ public class PatchBukkitPlayer
|
||||
|
||||
@Override
|
||||
public Locale locale() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'locale'");
|
||||
String locStr = getLocale();
|
||||
if (locStr != null && !locStr.isEmpty()) {
|
||||
String[] parts = locStr.split("_");
|
||||
if (parts.length >= 2) {
|
||||
return new Locale(parts[0], parts[1]);
|
||||
} else if (parts.length == 1) {
|
||||
return new Locale(parts[0]);
|
||||
}
|
||||
}
|
||||
return Locale.US;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1515,8 +1562,13 @@ public class PatchBukkitPlayer
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLocale'");
|
||||
try {
|
||||
var resp = NativeBridgeFfi.getPlayerLocale(BridgeUtils.convertUuid(this.uuid));
|
||||
if (resp != null && resp.getLocale() != null && !resp.getLocale().isEmpty()) {
|
||||
return resp.getLocale();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return "en_us";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1579,7 +1631,6 @@ public class PatchBukkitPlayer
|
||||
throw new UnsupportedOperationException("Unimplemented method 'openBook'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openVirtualSign(Position block, Side side) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'openVirtualSign'");
|
||||
@ -1765,7 +1816,6 @@ public class PatchBukkitPlayer
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setDeathScreenScore'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerGameConnection getConnection() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getConnection'");
|
||||
|
||||
@ -9,9 +9,29 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import patchbukkit.common.UUID;
|
||||
import patchbukkit.events.BlockBreakEvent;
|
||||
import patchbukkit.events.BlockDamageEvent;
|
||||
import patchbukkit.events.BlockPlaceEvent;
|
||||
import patchbukkit.events.Event;
|
||||
import patchbukkit.events.FireEventResponse;
|
||||
import patchbukkit.events.PlayerChatEvent;
|
||||
import patchbukkit.events.PlayerGameModeChangeEvent;
|
||||
import patchbukkit.events.PlayerInteractEvent;
|
||||
import patchbukkit.events.PlayerJoinEvent;
|
||||
import patchbukkit.events.PlayerInteractEntityEvent;
|
||||
import patchbukkit.events.PlayerMoveEvent;
|
||||
import patchbukkit.events.PlayerQuitEvent;
|
||||
import patchbukkit.events.PlayerToggleFlightEvent;
|
||||
import patchbukkit.events.PlayerToggleSneakEvent;
|
||||
import patchbukkit.events.PlayerToggleSprintEvent;
|
||||
import patchbukkit.events.PluginDisableEvent;
|
||||
import patchbukkit.events.PluginEnableEvent;
|
||||
import patchbukkit.events.BlockFadeEvent;
|
||||
import patchbukkit.events.BlockFormEvent;
|
||||
import patchbukkit.events.BlockGrowEvent;
|
||||
import patchbukkit.events.BlockIgniteEvent;
|
||||
import patchbukkit.events.ServerCommandEvent;
|
||||
import patchbukkit.events.SignChangeEvent;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -31,9 +51,10 @@ public class PatchBukkitEventFactory {
|
||||
|
||||
@Nullable
|
||||
public static org.bukkit.event.Event createEvent(@NotNull Event event) {
|
||||
Event.DataCase dataCase = event.getDataCase();
|
||||
try {
|
||||
Event.DataCase dataCase = event.getDataCase();
|
||||
|
||||
return switch (dataCase) {
|
||||
return switch (dataCase) {
|
||||
case PLAYER_JOIN -> {
|
||||
PlayerJoinEvent joinEvent = event.getPlayerJoin();
|
||||
Player player = getPlayer(joinEvent.getPlayerUuid().getValue());
|
||||
@ -42,22 +63,221 @@ public class PatchBukkitEventFactory {
|
||||
Component joinMessage = GsonComponentSerializer.gson().deserialize(joinEvent.getJoinMessage());
|
||||
yield new org.bukkit.event.player.PlayerJoinEvent(player, joinMessage);
|
||||
}
|
||||
case PLAYER_QUIT -> {
|
||||
PlayerQuitEvent quitEvent = event.getPlayerQuit();
|
||||
Player player = getPlayer(quitEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
|
||||
Component quitMessage = quitEvent.getQuitMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(quitEvent.getQuitMessage());
|
||||
yield new org.bukkit.event.player.PlayerQuitEvent(player, quitMessage);
|
||||
}
|
||||
case PLAYER_GAMEMODE_CHANGE -> {
|
||||
PlayerGameModeChangeEvent gmEvent = event.getPlayerGamemodeChange();
|
||||
Player player = getPlayer(gmEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
|
||||
org.bukkit.GameMode newGamemode;
|
||||
try {
|
||||
newGamemode = org.bukkit.GameMode.valueOf(gmEvent.getNewGamemode().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
newGamemode = org.bukkit.GameMode.SURVIVAL;
|
||||
}
|
||||
yield new org.bukkit.event.player.PlayerGameModeChangeEvent(player, newGamemode);
|
||||
}
|
||||
case PLAYER_INTERACT -> {
|
||||
PlayerInteractEvent interactEvent = event.getPlayerInteract();
|
||||
Player player = getPlayer(interactEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
|
||||
org.bukkit.event.block.Action action;
|
||||
try {
|
||||
action = org.bukkit.event.block.Action.valueOf(interactEvent.getAction());
|
||||
} catch (IllegalArgumentException e) {
|
||||
action = org.bukkit.event.block.Action.RIGHT_CLICK_AIR;
|
||||
}
|
||||
|
||||
org.bukkit.inventory.EquipmentSlot hand;
|
||||
try {
|
||||
hand = org.bukkit.inventory.EquipmentSlot.valueOf(interactEvent.getHand());
|
||||
} catch (IllegalArgumentException e) {
|
||||
hand = org.bukkit.inventory.EquipmentSlot.HAND;
|
||||
}
|
||||
|
||||
org.bukkit.block.Block clickedBlock = player.getWorld().getBlockAt(
|
||||
interactEvent.getClickedX(),
|
||||
interactEvent.getClickedY(),
|
||||
interactEvent.getClickedZ()
|
||||
);
|
||||
|
||||
yield new org.bukkit.event.player.PlayerInteractEvent(
|
||||
player,
|
||||
action,
|
||||
player.getInventory().getItemInMainHand(),
|
||||
clickedBlock,
|
||||
org.bukkit.block.BlockFace.SELF,
|
||||
hand
|
||||
);
|
||||
}
|
||||
case PLUGIN_ENABLE -> {
|
||||
PluginEnableEvent enableEvent = event.getPluginEnable();
|
||||
org.bukkit.plugin.Plugin plugin = Bukkit.getPluginManager().getPlugin(enableEvent.getPluginName());
|
||||
if (plugin == null) yield null;
|
||||
yield new org.bukkit.event.server.PluginEnableEvent(plugin);
|
||||
}
|
||||
case PLUGIN_DISABLE -> {
|
||||
PluginDisableEvent disableEvent = event.getPluginDisable();
|
||||
org.bukkit.plugin.Plugin plugin = Bukkit.getPluginManager().getPlugin(disableEvent.getPluginName());
|
||||
if (plugin == null) yield null;
|
||||
yield new org.bukkit.event.server.PluginDisableEvent(plugin);
|
||||
}
|
||||
case BLOCK_BREAK -> {
|
||||
BlockBreakEvent breakEvent = event.getBlockBreak();
|
||||
Player player = breakEvent.hasPlayerUuid() ? getPlayer(breakEvent.getPlayerUuid().getValue()) : null;
|
||||
if (player == null) yield null;
|
||||
org.bukkit.World world = player.getWorld();
|
||||
org.bukkit.block.Block block = world.getBlockAt(breakEvent.getBlockX(), breakEvent.getBlockY(), breakEvent.getBlockZ());
|
||||
yield new org.bukkit.event.block.BlockBreakEvent(block, player);
|
||||
}
|
||||
case BLOCK_PLACE -> {
|
||||
BlockPlaceEvent placeEvent = event.getBlockPlace();
|
||||
Player player = getPlayer(placeEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
org.bukkit.block.Block block = player.getWorld().getBlockAt(placeEvent.getBlockX(), placeEvent.getBlockY(), placeEvent.getBlockZ());
|
||||
org.bukkit.block.BlockState blockState = block.getState();
|
||||
yield new org.bukkit.event.block.BlockPlaceEvent(block, blockState, block, player.getInventory().getItemInMainHand(), player, placeEvent.getCanBuild(), org.bukkit.inventory.EquipmentSlot.HAND);
|
||||
}
|
||||
case SIGN_CHANGE -> {
|
||||
SignChangeEvent signEvent = event.getSignChange();
|
||||
Player player = getPlayer(signEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
org.bukkit.block.Block block = player.getWorld().getBlockAt(
|
||||
signEvent.getBlockX(),
|
||||
signEvent.getBlockY(),
|
||||
signEvent.getBlockZ()
|
||||
);
|
||||
String[] lines = signEvent.getLinesList().toArray(new String[0]);
|
||||
yield new org.bukkit.event.block.SignChangeEvent(block, player, lines);
|
||||
}
|
||||
case BLOCK_DAMAGE -> {
|
||||
BlockDamageEvent damageEvent = event.getBlockDamage();
|
||||
Player player = getPlayer(damageEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
org.bukkit.block.Block block = player.getWorld().getBlockAt(
|
||||
damageEvent.getBlockX(),
|
||||
damageEvent.getBlockY(),
|
||||
damageEvent.getBlockZ()
|
||||
);
|
||||
yield new org.bukkit.event.block.BlockDamageEvent(
|
||||
player, block, player.getInventory().getItemInMainHand(), damageEvent.getInstaBreak()
|
||||
);
|
||||
}
|
||||
case SERVER_COMMAND -> {
|
||||
ServerCommandEvent cmdEvent = event.getServerCommand();
|
||||
yield new org.bukkit.event.server.ServerCommandEvent(
|
||||
Bukkit.getConsoleSender(),
|
||||
cmdEvent.getCommand()
|
||||
);
|
||||
}
|
||||
case BLOCK_IGNITE -> {
|
||||
BlockIgniteEvent igniteEvent = event.getBlockIgnite();
|
||||
Player player = igniteEvent.hasPlayerUuid() ? getPlayer(igniteEvent.getPlayerUuid().getValue()) : null;
|
||||
org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
if (world == null) yield null;
|
||||
org.bukkit.block.Block block = world.getBlockAt(igniteEvent.getBlockX(), igniteEvent.getBlockY(), igniteEvent.getBlockZ());
|
||||
org.bukkit.event.block.BlockIgniteEvent.IgniteCause cause = org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL;
|
||||
yield new org.bukkit.event.block.BlockIgniteEvent(block, cause, player);
|
||||
}
|
||||
case BLOCK_GROW -> {
|
||||
BlockGrowEvent growEvent = event.getBlockGrow();
|
||||
org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
if (world == null) yield null;
|
||||
org.bukkit.block.Block block = world.getBlockAt(growEvent.getBlockX(), growEvent.getBlockY(), growEvent.getBlockZ());
|
||||
yield new org.bukkit.event.block.BlockGrowEvent(block, block.getState());
|
||||
}
|
||||
case BLOCK_FORM -> {
|
||||
BlockFormEvent formEvent = event.getBlockForm();
|
||||
org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
if (world == null) yield null;
|
||||
org.bukkit.block.Block block = world.getBlockAt(formEvent.getBlockX(), formEvent.getBlockY(), formEvent.getBlockZ());
|
||||
yield new org.bukkit.event.block.BlockFormEvent(block, block.getState());
|
||||
}
|
||||
case BLOCK_FADE -> {
|
||||
BlockFadeEvent fadeEvent = event.getBlockFade();
|
||||
org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
if (world == null) yield null;
|
||||
org.bukkit.block.Block block = world.getBlockAt(fadeEvent.getBlockX(), fadeEvent.getBlockY(), fadeEvent.getBlockZ());
|
||||
yield new org.bukkit.event.block.BlockFadeEvent(block, block.getState());
|
||||
}
|
||||
case PLAYER_TOGGLE_SNEAK -> {
|
||||
PlayerToggleSneakEvent sneakEvent = event.getPlayerToggleSneak();
|
||||
Player player = getPlayer(sneakEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
yield new org.bukkit.event.player.PlayerToggleSneakEvent(player, sneakEvent.getIsSneaking());
|
||||
}
|
||||
case PLAYER_TOGGLE_SPRINT -> {
|
||||
PlayerToggleSprintEvent sprintEvent = event.getPlayerToggleSprint();
|
||||
Player player = getPlayer(sprintEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
yield new org.bukkit.event.player.PlayerToggleSprintEvent(player, sprintEvent.getIsSprinting());
|
||||
}
|
||||
case PLAYER_TOGGLE_FLIGHT -> {
|
||||
PlayerToggleFlightEvent flightEvent = event.getPlayerToggleFlight();
|
||||
Player player = getPlayer(flightEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
yield new org.bukkit.event.player.PlayerToggleFlightEvent(player, flightEvent.getIsFlying());
|
||||
}
|
||||
case PLAYER_MOVE -> {
|
||||
PlayerMoveEvent moveEvent = event.getPlayerMove();
|
||||
Player player = getPlayer(moveEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
org.bukkit.Location from = org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getFrom());
|
||||
org.bukkit.Location to = org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getTo());
|
||||
yield new org.bukkit.event.player.PlayerMoveEvent(player, from, to);
|
||||
}
|
||||
case PLAYER_INTERACT_ENTITY -> {
|
||||
PlayerInteractEntityEvent interactEvent = event.getPlayerInteractEntity();
|
||||
Player player = getPlayer(interactEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
java.util.UUID targetUuid = org.patchbukkit.bridge.BridgeUtils.convertUuid(interactEvent.getTargetUuid());
|
||||
org.bukkit.entity.Entity target = Bukkit.getPlayer(targetUuid);
|
||||
if (target == null) target = Bukkit.getEntity(targetUuid);
|
||||
if (target == null) yield null;
|
||||
if ("ATTACK".equals(interactEvent.getAction())) {
|
||||
yield new org.bukkit.event.entity.EntityDamageByEntityEvent(
|
||||
player, target, org.bukkit.event.entity.EntityDamageEvent.DamageCause.ENTITY_ATTACK, 1.0
|
||||
);
|
||||
} else {
|
||||
yield new org.bukkit.event.player.PlayerInteractEntityEvent(player, target);
|
||||
}
|
||||
}
|
||||
case PLAYER_CHAT -> {
|
||||
PlayerChatEvent chatEvent = event.getPlayerChat();
|
||||
Player player = getPlayer(chatEvent.getPlayerUuid().getValue());
|
||||
if (player == null) yield null;
|
||||
yield new org.bukkit.event.player.AsyncPlayerChatEvent(true, player, chatEvent.getMessage(), new java.util.HashSet<>(Bukkit.getOnlinePlayers()));
|
||||
}
|
||||
case DATA_NOT_SET -> {
|
||||
LOGGER.warning("EventFactory: Received Event with no data");
|
||||
yield null;
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
} catch (Throwable t) {
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception in createEvent for " + event.getDataCase() + ": " + t.getMessage(), t);
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static byte[] toFireEventResponse(@NotNull org.bukkit.event.Event event) {
|
||||
boolean cancelled = event instanceof org.bukkit.event.Cancellable c && c.isCancelled();
|
||||
try {
|
||||
boolean cancelled = event instanceof org.bukkit.event.Cancellable c && c.isCancelled();
|
||||
|
||||
FireEventResponse.Builder builder = FireEventResponse.newBuilder()
|
||||
.setCancelled(cancelled);
|
||||
FireEventResponse.Builder builder = FireEventResponse.newBuilder()
|
||||
.setCancelled(cancelled);
|
||||
|
||||
Event.Builder eventBuilder = Event.newBuilder();
|
||||
Event.Builder eventBuilder = Event.newBuilder();
|
||||
|
||||
if (event instanceof org.bukkit.event.player.PlayerJoinEvent joinEvent) {
|
||||
String joinMessage = GsonComponentSerializer.gson().serialize(joinEvent.joinMessage());
|
||||
@ -70,11 +290,131 @@ public class PatchBukkitEventFactory {
|
||||
.setJoinMessage(joinMessage)
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerQuitEvent quitEvent) {
|
||||
String quitMessage = quitEvent.quitMessage() != null ? GsonComponentSerializer.gson().serialize(quitEvent.quitMessage()) : "";
|
||||
|
||||
eventBuilder.setPlayerQuit(
|
||||
PlayerQuitEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder()
|
||||
.setValue(quitEvent.getPlayer().getUniqueId().toString())
|
||||
.build())
|
||||
.setQuitMessage(quitMessage)
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerGameModeChangeEvent gmEvent) {
|
||||
eventBuilder.setPlayerGamemodeChange(
|
||||
PlayerGameModeChangeEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder()
|
||||
.setValue(gmEvent.getPlayer().getUniqueId().toString())
|
||||
.build())
|
||||
.setNewGamemode(gmEvent.getNewGameMode().name())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerInteractEvent interactEvent) {
|
||||
var block = interactEvent.getClickedBlock();
|
||||
int x = block != null ? block.getX() : 0;
|
||||
int y = block != null ? block.getY() : 0;
|
||||
int z = block != null ? block.getZ() : 0;
|
||||
|
||||
eventBuilder.setPlayerInteract(
|
||||
PlayerInteractEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder()
|
||||
.setValue(interactEvent.getPlayer().getUniqueId().toString())
|
||||
.build())
|
||||
.setAction(interactEvent.getAction().name())
|
||||
.setClickedX(x)
|
||||
.setClickedY(y)
|
||||
.setClickedZ(z)
|
||||
.setBlockFace(interactEvent.getBlockFace().name())
|
||||
.setHand(interactEvent.getHand() != null ? interactEvent.getHand().name() : "HAND")
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.server.PluginEnableEvent enableEvent) {
|
||||
eventBuilder.setPluginEnable(
|
||||
PluginEnableEvent.newBuilder()
|
||||
.setPluginName(enableEvent.getPlugin().getName())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.server.PluginDisableEvent disableEvent) {
|
||||
eventBuilder.setPluginDisable(
|
||||
PluginDisableEvent.newBuilder()
|
||||
.setPluginName(disableEvent.getPlugin().getName())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.block.BlockBreakEvent breakEvent) {
|
||||
var b = breakEvent.getBlock();
|
||||
var bBuilder = BlockBreakEvent.newBuilder()
|
||||
.setBlockX(b.getX())
|
||||
.setBlockY(b.getY())
|
||||
.setBlockZ(b.getZ())
|
||||
.setBlockType(b.getType().name())
|
||||
.setExp(breakEvent.getExpToDrop())
|
||||
.setDropItems(breakEvent.isDropItems());
|
||||
if (breakEvent.getPlayer() != null) {
|
||||
bBuilder.setPlayerUuid(UUID.newBuilder().setValue(breakEvent.getPlayer().getUniqueId().toString()).build());
|
||||
}
|
||||
eventBuilder.setBlockBreak(bBuilder.build());
|
||||
} else if (event instanceof org.bukkit.event.block.BlockPlaceEvent placeEvent) {
|
||||
var b = placeEvent.getBlockPlaced();
|
||||
var against = placeEvent.getBlockAgainst();
|
||||
eventBuilder.setBlockPlace(
|
||||
BlockPlaceEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(placeEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setBlockX(b.getX())
|
||||
.setBlockY(b.getY())
|
||||
.setBlockZ(b.getZ())
|
||||
.setBlockPlacedType(b.getType().name())
|
||||
.setBlockAgainstType(against.getType().name())
|
||||
.setCanBuild(placeEvent.canBuild())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerToggleSneakEvent sneakEvent) {
|
||||
eventBuilder.setPlayerToggleSneak(
|
||||
PlayerToggleSneakEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(sneakEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setIsSneaking(sneakEvent.isSneaking())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerToggleSprintEvent sprintEvent) {
|
||||
eventBuilder.setPlayerToggleSprint(
|
||||
PlayerToggleSprintEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(sprintEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setIsSprinting(sprintEvent.isSprinting())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerToggleFlightEvent flightEvent) {
|
||||
eventBuilder.setPlayerToggleFlight(
|
||||
PlayerToggleFlightEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(flightEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setIsFlying(flightEvent.isFlying())
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.PlayerMoveEvent moveEvent) {
|
||||
eventBuilder.setPlayerMove(
|
||||
PlayerMoveEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(moveEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setFrom(org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getFrom()))
|
||||
.setTo(org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getTo()))
|
||||
.build()
|
||||
);
|
||||
} else if (event instanceof org.bukkit.event.player.AsyncPlayerChatEvent chatEvent) {
|
||||
eventBuilder.setPlayerChat(
|
||||
PlayerChatEvent.newBuilder()
|
||||
.setPlayerUuid(UUID.newBuilder().setValue(chatEvent.getPlayer().getUniqueId().toString()).build())
|
||||
.setMessage(chatEvent.getMessage())
|
||||
.setFormat(chatEvent.getFormat())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
builder.setData(eventBuilder.build());
|
||||
|
||||
return builder.build().toByteArray();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception serializing event response for " + event.getEventName() + ": " + t.getMessage(), t);
|
||||
t.printStackTrace();
|
||||
return FireEventResponse.newBuilder().setCancelled(false).build().toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isCancellable(@NotNull org.bukkit.event.Event event) {
|
||||
|
||||
@ -20,8 +20,21 @@ import co.aikar.timings.TimedEventExecutor;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
import patchbukkit.events.BlockBreakEvent;
|
||||
import patchbukkit.events.BlockPlaceEvent;
|
||||
import patchbukkit.events.CallEventRequest;
|
||||
import patchbukkit.events.PlayerChatEvent;
|
||||
import patchbukkit.events.PlayerGameModeChangeEvent;
|
||||
import patchbukkit.events.PlayerInteractEvent;
|
||||
import patchbukkit.events.PlayerJoinEvent;
|
||||
import patchbukkit.events.PlayerInteractEntityEvent;
|
||||
import patchbukkit.events.PlayerMoveEvent;
|
||||
import patchbukkit.events.PlayerQuitEvent;
|
||||
import patchbukkit.events.PlayerToggleFlightEvent;
|
||||
import patchbukkit.events.PlayerToggleSneakEvent;
|
||||
import patchbukkit.events.PlayerToggleSprintEvent;
|
||||
import patchbukkit.events.PluginDisableEvent;
|
||||
import patchbukkit.events.PluginEnableEvent;
|
||||
import patchbukkit.events.RegisterEventRequest;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@ -59,6 +72,262 @@ public class PatchBukkitEventManager {
|
||||
).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());
|
||||
|
||||
@ -107,6 +376,7 @@ public class PatchBukkitEventManager {
|
||||
+ " to " + registration.getPlugin().getPluginMeta().getDisplayName(),
|
||||
ex
|
||||
);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,6 +405,7 @@ public class PatchBukkitEventManager {
|
||||
+ " to " + listener.getPlugin().getPluginMeta().getDisplayName(),
|
||||
ex
|
||||
);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package org.patchbukkit.help;
|
||||
|
||||
import org.bukkit.help.HelpMap;
|
||||
import org.bukkit.help.HelpTopic;
|
||||
import org.bukkit.help.HelpTopicFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PatchBukkitHelpMap implements HelpMap {
|
||||
|
||||
private final Map<String, HelpTopic> helpTopics = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, HelpTopicFactory<?>> topicFactories = new ConcurrentHashMap<>();
|
||||
private final List<String> ignoredPlugins = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public @Nullable HelpTopic getHelpTopic(@NonNull String topicName) {
|
||||
if (topicName == null) return null;
|
||||
return helpTopics.get(topicName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<HelpTopic> getHelpTopics() {
|
||||
return Collections.unmodifiableCollection(helpTopics.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopic(@NonNull HelpTopic topic) {
|
||||
if (topic != null && topic.getName() != null) {
|
||||
helpTopics.put(topic.getName(), topic);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
helpTopics.clear();
|
||||
topicFactories.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void registerHelpTopicFactory(@NonNull Class<?> commandClass, @NonNull HelpTopicFactory<?> factory) {
|
||||
if (commandClass != null && factory != null) {
|
||||
topicFactories.put(commandClass, factory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<String> getIgnoredPlugins() {
|
||||
return Collections.unmodifiableList(ignoredPlugins);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
package org.patchbukkit.inventory;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.patchbukkit.entity.PatchBukkitHumanEntity;
|
||||
|
||||
public class PatchBukkitEntityEquipment implements EntityEquipment {
|
||||
private final PatchBukkitHumanEntity holder;
|
||||
|
||||
public PatchBukkitEntityEquipment(PatchBukkitHumanEntity holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInMainHand() {
|
||||
return holder.getInventory().getItemInMainHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(ItemStack item) {
|
||||
holder.getInventory().setItemInMainHand(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(ItemStack item, boolean silent) {
|
||||
setItemInMainHand(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInOffHand() {
|
||||
return holder.getInventory().getItemInOffHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(ItemStack item) {
|
||||
holder.getInventory().setItemInOffHand(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(ItemStack item, boolean silent) {
|
||||
setItemInOffHand(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInHand(ItemStack stack) {
|
||||
setItemInMainHand(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getHelmet() {
|
||||
return holder.getInventory().getHelmet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(ItemStack helmet) {
|
||||
holder.getInventory().setHelmet(helmet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(ItemStack helmet, boolean silent) {
|
||||
setHelmet(helmet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getChestplate() {
|
||||
return holder.getInventory().getChestplate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(ItemStack chestplate) {
|
||||
holder.getInventory().setChestplate(chestplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(ItemStack chestplate, boolean silent) {
|
||||
setChestplate(chestplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getLeggings() {
|
||||
return holder.getInventory().getLeggings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(ItemStack leggings) {
|
||||
holder.getInventory().setLeggings(leggings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(ItemStack leggings, boolean silent) {
|
||||
setLeggings(leggings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getBoots() {
|
||||
return holder.getInventory().getBoots();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(ItemStack boots) {
|
||||
holder.getInventory().setBoots(boots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(ItemStack boots, boolean silent) {
|
||||
setBoots(boots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getArmorContents() {
|
||||
return holder.getInventory().getArmorContents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArmorContents(ItemStack[] items) {
|
||||
holder.getInventory().setArmorContents(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
holder.getInventory().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getHolder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EquipmentSlot slot, ItemStack item) {
|
||||
holder.getInventory().setItem(slot, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EquipmentSlot slot, ItemStack item, boolean silent) {
|
||||
setItem(slot, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(EquipmentSlot slot) {
|
||||
return holder.getInventory().getItem(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDropChance(EquipmentSlot slot, float chance) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDropChance(EquipmentSlot slot) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getItemInMainHandDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setItemInMainHandDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getItemInOffHandDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setItemInOffHandDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getItemInHandDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setItemInHandDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getHelmetDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setHelmetDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getChestplateDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setChestplateDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getLeggingsDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setLeggingsDropChance(float chance) {}
|
||||
|
||||
@Override
|
||||
public float getBootsDropChance() { return 1.0f; }
|
||||
@Override
|
||||
public void setBootsDropChance(float chance) {}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package org.patchbukkit.inventory;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemFactory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
public class PatchBukkitItemFactory {
|
||||
public static final ItemFactory INSTANCE = createFactory();
|
||||
|
||||
private static ItemFactory createFactory() {
|
||||
return (ItemFactory) Proxy.newProxyInstance(
|
||||
ItemFactory.class.getClassLoader(),
|
||||
new Class<?>[] { ItemFactory.class },
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("getItemMeta".equals(name) || "createItemMeta".equals(name) || "asMetaFor".equals(name)) {
|
||||
if (args != null && args.length > 0 && args[0] instanceof Material mat && mat == Material.AIR) {
|
||||
return null;
|
||||
}
|
||||
return createMeta();
|
||||
}
|
||||
if ("isApplicable".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
if ("equals".equals(name)) {
|
||||
if (args != null && args.length == 2) {
|
||||
return Objects.equals(args[0], args[1]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ("ensureServerConform".equals(name)) {
|
||||
if (args != null && args.length > 0 && args[0] instanceof ItemStack stack) {
|
||||
if (stack instanceof PatchBukkitItemStack) {
|
||||
return stack;
|
||||
}
|
||||
try {
|
||||
return new PatchBukkitItemStack(stack.getType(), stack.getAmount());
|
||||
} catch (Throwable ignored) {
|
||||
return new PatchBukkitItemStack(Material.AIR, 0);
|
||||
}
|
||||
}
|
||||
return new PatchBukkitItemStack(Material.AIR, 0);
|
||||
}
|
||||
if ("isItemEmpty".equals(name)) {
|
||||
if (args != null && args.length > 0 && args[0] instanceof ItemStack stack) {
|
||||
return PatchBukkitPlayerInventory.isItemEmpty(stack);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == boolean.class) return false;
|
||||
if (returnType == int.class) return 0;
|
||||
if (returnType == long.class) return 0L;
|
||||
if (returnType == double.class || returnType == float.class) return 0.0;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static ItemMeta createMeta() {
|
||||
Map<String, Object> state = new HashMap<>();
|
||||
return (ItemMeta) Proxy.newProxyInstance(
|
||||
ItemMeta.class.getClassLoader(),
|
||||
new Class<?>[] { ItemMeta.class },
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("hasDisplayName".equals(name)) return state.containsKey("displayName");
|
||||
if ("getDisplayName".equals(name)) return state.get("displayName");
|
||||
if ("setDisplayName".equals(name)) {
|
||||
if (args != null && args.length > 0 && args[0] != null) state.put("displayName", args[0]);
|
||||
else state.remove("displayName");
|
||||
return null;
|
||||
}
|
||||
if ("hasLore".equals(name)) return state.containsKey("lore");
|
||||
if ("getLore".equals(name)) return state.get("lore");
|
||||
if ("setLore".equals(name)) {
|
||||
if (args != null && args.length > 0 && args[0] != null) state.put("lore", args[0]);
|
||||
else state.remove("lore");
|
||||
return null;
|
||||
}
|
||||
if ("clone".equals(name)) return createMeta();
|
||||
if ("equals".equals(name)) {
|
||||
if (args != null && args.length > 0) return proxy == args[0];
|
||||
return false;
|
||||
}
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == boolean.class) return false;
|
||||
if (returnType == int.class) return 0;
|
||||
if (returnType == long.class) return 0L;
|
||||
if (returnType == double.class || returnType == float.class) return 0.0;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package org.patchbukkit.inventory;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PatchBukkitItemStack extends ItemStack {
|
||||
|
||||
private Material type;
|
||||
private int amount;
|
||||
private ItemMeta meta;
|
||||
|
||||
public PatchBukkitItemStack(Material type) {
|
||||
this(type, 1);
|
||||
}
|
||||
|
||||
public PatchBukkitItemStack(Material type, int amount) {
|
||||
this.type = type != null ? type : Material.AIR;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Material getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(@Nullable Material type) {
|
||||
this.type = type != null ? type : Material.AIR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAmount(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemMeta getItemMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setItemMeta(@Nullable ItemMeta itemMeta) {
|
||||
this.meta = itemMeta;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasItemMeta() {
|
||||
return meta != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return type == Material.AIR || amount <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ItemStack clone() {
|
||||
PatchBukkitItemStack cloned = new PatchBukkitItemStack(type, amount);
|
||||
if (meta != null) {
|
||||
cloned.meta = meta.clone();
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof ItemStack other)) return false;
|
||||
return this.type == other.getType() && this.amount == other.getAmount() && Objects.equals(this.meta, other.getItemMeta());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, amount, meta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ItemStack{" + type + " x " + amount + "}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,539 @@
|
||||
package org.patchbukkit.inventory;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.patchbukkit.bridge.BridgeUtils;
|
||||
import org.patchbukkit.entity.PatchBukkitHumanEntity;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
import patchbukkit.itemstack.GetPlayerInventoryResponse;
|
||||
import patchbukkit.itemstack.SetPlayerEquipmentRequest;
|
||||
import patchbukkit.itemstack.SetPlayerInventorySlotRequest;
|
||||
import patchbukkit.itemstack.SetPlayerSelectedSlotRequest;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PatchBukkitPlayerInventory implements PlayerInventory {
|
||||
private final PatchBukkitHumanEntity holder;
|
||||
|
||||
public PatchBukkitPlayerInventory(PatchBukkitHumanEntity holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
private GetPlayerInventoryResponse fetchInventory() {
|
||||
var resp = NativeBridgeFfi.getPlayerInventory(BridgeUtils.convertUuid(holder.getUniqueId()));
|
||||
if (resp == null) {
|
||||
return GetPlayerInventoryResponse.newBuilder().build();
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
public static boolean isItemEmpty(@Nullable ItemStack item) {
|
||||
if (item == null) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (item.getType() == Material.AIR || item.getAmount() <= 0) {
|
||||
return true;
|
||||
}
|
||||
return item.isEmpty();
|
||||
} catch (Throwable ignored) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static patchbukkit.itemstack.ItemStack toProto(ItemStack item) {
|
||||
if (isItemEmpty(item)) {
|
||||
return patchbukkit.itemstack.ItemStack.newBuilder()
|
||||
.setType("minecraft:air")
|
||||
.setAmount(0)
|
||||
.build();
|
||||
}
|
||||
try {
|
||||
String type = item.getType().getKey().toString();
|
||||
return patchbukkit.itemstack.ItemStack.newBuilder()
|
||||
.setType(type)
|
||||
.setAmount(item.getAmount())
|
||||
.build();
|
||||
} catch (Throwable ignored) {
|
||||
return patchbukkit.itemstack.ItemStack.newBuilder()
|
||||
.setType("minecraft:air")
|
||||
.setAmount(0)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack fromProto(patchbukkit.itemstack.ItemStack proto) {
|
||||
if (proto == null || proto.getAmount() <= 0 || proto.getType() == null || proto.getType().isEmpty() || proto.getType().equalsIgnoreCase("minecraft:air")) {
|
||||
return new PatchBukkitItemStack(Material.AIR, 0);
|
||||
}
|
||||
Material mat = Material.matchMaterial(proto.getType());
|
||||
if (mat == null || mat == Material.AIR) {
|
||||
return new PatchBukkitItemStack(Material.AIR, 0);
|
||||
}
|
||||
return new PatchBukkitItemStack(mat, proto.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<ItemStack> iterator() {
|
||||
return Arrays.asList(getContents()).listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<ItemStack> iterator(int index) {
|
||||
return Arrays.asList(getContents()).listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getArmorContents() {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
ItemStack[] armor = new ItemStack[4];
|
||||
armor[0] = fromProto(resp.getBoots());
|
||||
armor[1] = fromProto(resp.getLeggings());
|
||||
armor[2] = fromProto(resp.getChestplate());
|
||||
armor[3] = fromProto(resp.getHelmet());
|
||||
return armor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArmorContents(ItemStack[] items) {
|
||||
if (items == null) items = new ItemStack[4];
|
||||
setBoots(items.length > 0 ? items[0] : null);
|
||||
setLeggings(items.length > 1 ? items[1] : null);
|
||||
setChestplate(items.length > 2 ? items[2] : null);
|
||||
setHelmet(items.length > 3 ? items[3] : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getExtraContents() {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
return new ItemStack[] { fromProto(resp.getOffHand()) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtraContents(ItemStack[] items) {
|
||||
if (items != null && items.length > 0) {
|
||||
setItemInOffHand(items[0]);
|
||||
} else {
|
||||
setItemInOffHand(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getHelmet() {
|
||||
return fromProto(fetchInventory().getHelmet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getChestplate() {
|
||||
return fromProto(fetchInventory().getChestplate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getLeggings() {
|
||||
return fromProto(fetchInventory().getLeggings());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getBoots() {
|
||||
return fromProto(fetchInventory().getBoots());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(ItemStack helmet) {
|
||||
setEquipment(5, helmet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(ItemStack chestplate) {
|
||||
setEquipment(4, chestplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(ItemStack leggings) {
|
||||
setEquipment(3, leggings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(ItemStack boots) {
|
||||
setEquipment(2, boots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInMainHand() {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
int selected = resp.getSelectedSlot();
|
||||
if (selected >= 0 && selected < resp.getMainInventoryCount()) {
|
||||
return fromProto(resp.getMainInventory(selected));
|
||||
}
|
||||
return ItemStack.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(ItemStack item) {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
int selected = resp.getSelectedSlot();
|
||||
setItem(selected, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInOffHand() {
|
||||
return fromProto(fetchInventory().getOffHand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(ItemStack item) {
|
||||
setEquipment(1, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInHand(ItemStack stack) {
|
||||
setItemInMainHand(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeldItemSlot() {
|
||||
return fetchInventory().getSelectedSlot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeldItemSlot(int slot) {
|
||||
if (slot >= 0 && slot < 9) {
|
||||
NativeBridgeFfi.setPlayerSelectedSlot(
|
||||
SetPlayerSelectedSlotRequest.newBuilder()
|
||||
.setUuid(BridgeUtils.convertUuid(holder.getUniqueId()))
|
||||
.setSlot(slot)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEquipment(int slotType, ItemStack item) {
|
||||
NativeBridgeFfi.setPlayerEquipment(
|
||||
SetPlayerEquipmentRequest.newBuilder()
|
||||
.setUuid(BridgeUtils.convertUuid(holder.getUniqueId()))
|
||||
.setSlotType(slotType)
|
||||
.setItem(toProto(item))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HumanEntity getHolder() {
|
||||
return this.holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HumanEntity getHolder(boolean useSnapshot) {
|
||||
return this.holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 41;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxStackSize() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxStackSize(int size) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(int index) {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
if (index >= 0 && index < resp.getMainInventoryCount()) {
|
||||
return fromProto(resp.getMainInventory(index));
|
||||
} else if (index == 36) {
|
||||
return fromProto(resp.getBoots());
|
||||
} else if (index == 37) {
|
||||
return fromProto(resp.getLeggings());
|
||||
} else if (index == 38) {
|
||||
return fromProto(resp.getChestplate());
|
||||
} else if (index == 39) {
|
||||
return fromProto(resp.getHelmet());
|
||||
} else if (index == 40) {
|
||||
return fromProto(resp.getOffHand());
|
||||
}
|
||||
return ItemStack.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(int index, ItemStack item) {
|
||||
NativeBridgeFfi.setPlayerInventorySlot(
|
||||
SetPlayerInventorySlotRequest.newBuilder()
|
||||
.setUuid(BridgeUtils.convertUuid(holder.getUniqueId()))
|
||||
.setSlot(index)
|
||||
.setItem(toProto(item))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EquipmentSlot slot, ItemStack item) {
|
||||
if (slot == null) return;
|
||||
switch (slot) {
|
||||
case HAND -> setItemInMainHand(item);
|
||||
case OFF_HAND -> setItemInOffHand(item);
|
||||
case FEET -> setBoots(item);
|
||||
case LEGS -> setLeggings(item);
|
||||
case CHEST -> setChestplate(item);
|
||||
case HEAD -> setHelmet(item);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(EquipmentSlot slot) {
|
||||
if (slot == null) return ItemStack.empty();
|
||||
return switch (slot) {
|
||||
case HAND -> getItemInMainHand();
|
||||
case OFF_HAND -> getItemInOffHand();
|
||||
case FEET -> getBoots();
|
||||
case LEGS -> getLeggings();
|
||||
case CHEST -> getChestplate();
|
||||
case HEAD -> getHelmet();
|
||||
default -> ItemStack.empty();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, ItemStack> addItem(ItemStack... items) throws IllegalArgumentException {
|
||||
HashMap<Integer, ItemStack> leftover = new HashMap<>();
|
||||
if (items == null) return leftover;
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
ItemStack item = items[i];
|
||||
if (isItemEmpty(item)) continue;
|
||||
int firstEmpty = firstEmpty();
|
||||
if (firstEmpty != -1) {
|
||||
setItem(firstEmpty, item);
|
||||
} else {
|
||||
leftover.put(i, item);
|
||||
}
|
||||
}
|
||||
return leftover;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, ItemStack> removeItemAnySlot(ItemStack... items) throws IllegalArgumentException {
|
||||
return removeItem(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, ItemStack> removeItem(ItemStack... items) throws IllegalArgumentException {
|
||||
HashMap<Integer, ItemStack> leftover = new HashMap<>();
|
||||
if (items == null) return leftover;
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
ItemStack item = items[i];
|
||||
if (isItemEmpty(item)) continue;
|
||||
int slot = first(item.getType());
|
||||
if (slot != -1) {
|
||||
setItem(slot, ItemStack.empty());
|
||||
} else {
|
||||
leftover.put(i, item);
|
||||
}
|
||||
}
|
||||
return leftover;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getContents() {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
ItemStack[] contents = new ItemStack[41];
|
||||
for (int i = 0; i < 36 && i < resp.getMainInventoryCount(); i++) {
|
||||
contents[i] = fromProto(resp.getMainInventory(i));
|
||||
}
|
||||
for (int i = resp.getMainInventoryCount(); i < 36; i++) {
|
||||
contents[i] = ItemStack.empty();
|
||||
}
|
||||
contents[36] = fromProto(resp.getBoots());
|
||||
contents[37] = fromProto(resp.getLeggings());
|
||||
contents[38] = fromProto(resp.getChestplate());
|
||||
contents[39] = fromProto(resp.getHelmet());
|
||||
contents[40] = fromProto(resp.getOffHand());
|
||||
return contents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContents(ItemStack[] items) throws IllegalArgumentException {
|
||||
if (items == null) return;
|
||||
for (int i = 0; i < items.length && i < 41; i++) {
|
||||
setItem(i, items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getStorageContents() {
|
||||
GetPlayerInventoryResponse resp = fetchInventory();
|
||||
ItemStack[] contents = new ItemStack[36];
|
||||
for (int i = 0; i < 36 && i < resp.getMainInventoryCount(); i++) {
|
||||
contents[i] = fromProto(resp.getMainInventory(i));
|
||||
}
|
||||
for (int i = resp.getMainInventoryCount(); i < 36; i++) {
|
||||
contents[i] = ItemStack.empty();
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
|
||||
if (items == null) return;
|
||||
for (int i = 0; i < items.length && i < 36; i++) {
|
||||
setItem(i, items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Material material) throws IllegalArgumentException {
|
||||
return first(material) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ItemStack item) {
|
||||
return first(item) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Material material, int amount) throws IllegalArgumentException {
|
||||
if (amount <= 0) return true;
|
||||
int count = 0;
|
||||
for (ItemStack is : getStorageContents()) {
|
||||
if (is != null && !is.isEmpty() && is.getType() == material) {
|
||||
count += is.getAmount();
|
||||
if (count >= amount) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ItemStack item, int amount) {
|
||||
if (item == null) return false;
|
||||
return contains(item.getType(), amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAtLeast(ItemStack item, int amount) {
|
||||
if (item == null) return false;
|
||||
return contains(item.getType(), amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, ? extends ItemStack> all(Material material) throws IllegalArgumentException {
|
||||
HashMap<Integer, ItemStack> slots = new HashMap<>();
|
||||
ItemStack[] contents = getContents();
|
||||
for (int i = 0; i < contents.length; i++) {
|
||||
if (contents[i] != null && !contents[i].isEmpty() && contents[i].getType() == material) {
|
||||
slots.put(i, contents[i]);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, ? extends ItemStack> all(ItemStack item) {
|
||||
if (item == null) return new HashMap<>();
|
||||
return all(item.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(Material material) throws IllegalArgumentException {
|
||||
if (material == null) return -1;
|
||||
ItemStack[] contents = getContents();
|
||||
for (int i = 0; i < contents.length; i++) {
|
||||
if (contents[i] != null && !contents[i].isEmpty() && contents[i].getType() == material) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int first(ItemStack item) {
|
||||
if (item == null) return -1;
|
||||
return first(item.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstEmpty() {
|
||||
ItemStack[] contents = getStorageContents();
|
||||
for (int i = 0; i < contents.length; i++) {
|
||||
if (isItemEmpty(contents[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
for (ItemStack item : getContents()) {
|
||||
if (!isItemEmpty(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Material material) throws IllegalArgumentException {
|
||||
if (material == null) return;
|
||||
ItemStack[] contents = getContents();
|
||||
for (int i = 0; i < contents.length; i++) {
|
||||
if (contents[i] != null && contents[i].getType() == material) {
|
||||
setItem(i, ItemStack.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(ItemStack item) {
|
||||
if (item != null) {
|
||||
remove(item.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int index) {
|
||||
setItem(index, ItemStack.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
NativeBridgeFfi.clearPlayerInventory(BridgeUtils.convertUuid(holder.getUniqueId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HumanEntity> getViewers() {
|
||||
return Collections.singletonList(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InventoryType getType() {
|
||||
return InventoryType.PLAYER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int close() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getLocation() {
|
||||
return holder.getLocation();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,269 @@
|
||||
package org.patchbukkit.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.patchbukkit.PatchBukkitPluginManager;
|
||||
import org.patchbukkit.command.CommandFactory;
|
||||
import org.patchbukkit.command.PatchBukkitCommandMap;
|
||||
|
||||
public class 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);
|
||||
return true;
|
||||
}
|
||||
|
||||
List<File> jarFiles = new ArrayList<>();
|
||||
findJarsRecursive(pluginsDir, jarFiles);
|
||||
|
||||
if (jarFiles.isEmpty()) {
|
||||
System.out.println("[PatchBukkit] No plugin JAR files found in: " + pluginsDirPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<String, PluginHolder> holders = new LinkedHashMap<>();
|
||||
|
||||
for (File jarFile : jarFiles) {
|
||||
try {
|
||||
PluginHolder holder = parsePluginHolder(jarFile);
|
||||
if (holder != null) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
List<PluginHolder> loadOrder = computeLoadOrder(holders);
|
||||
|
||||
for (PluginHolder holder : loadOrder) {
|
||||
try {
|
||||
loadAndRegisterPlugin(holder, holders);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Failed to load plugin " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable all instantiated plugins in dependency order
|
||||
if (Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) {
|
||||
for (PluginHolder holder : loadOrder) {
|
||||
if (holder.pluginInstance != null) {
|
||||
try {
|
||||
pm.enablePlugin(holder.pluginInstance);
|
||||
System.out.println("[PatchBukkit] Enabled plugin: " + holder.name);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error enabling plugin " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Fatal error during bootstrap: " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void findJarsRecursive(File dir, List<File> result) {
|
||||
if (dir == null || !dir.isDirectory()) return;
|
||||
if (dir.getName().equals("patchbukkit-libs")) return;
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) return;
|
||||
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
if (!file.getName().equals("patchbukkit-libs")) {
|
||||
findJarsRecursive(file, result);
|
||||
}
|
||||
} else if (file.getName().endsWith(".jar")) {
|
||||
result.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PluginHolder parsePluginHolder(File jarFile) throws Exception {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
JarEntry entry = jar.getJarEntry("plugin.yml");
|
||||
if (entry == null) {
|
||||
entry = jar.getJarEntry("paper-plugin.yml");
|
||||
}
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PluginDescriptionFile description;
|
||||
try (InputStream in = jar.getInputStream(entry)) {
|
||||
description = new PluginDescriptionFile(in);
|
||||
}
|
||||
|
||||
PluginHolder holder = new PluginHolder();
|
||||
holder.jarFile = jarFile;
|
||||
holder.description = description;
|
||||
holder.name = description.getName();
|
||||
holder.mainClass = description.getMain();
|
||||
holder.depends = description.getDepend() != null ? description.getDepend() : Collections.emptyList();
|
||||
holder.softDepends = description.getSoftDepend() != null ? description.getSoftDepend() : Collections.emptyList();
|
||||
holder.provides = description.getProvides() != null ? description.getProvides() : Collections.emptyList();
|
||||
holder.libraries = description.getLibraries() != null ? description.getLibraries() : Collections.emptyList();
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PluginHolder> computeLoadOrder(Map<String, PluginHolder> holders) {
|
||||
List<PluginHolder> order = new ArrayList<>();
|
||||
Set<String> visited = new HashSet<>();
|
||||
Set<String> visiting = new HashSet<>();
|
||||
|
||||
for (PluginHolder holder : holders.values()) {
|
||||
visitHolder(holder, holders, visited, visiting, order);
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static void visitHolder(
|
||||
PluginHolder holder,
|
||||
Map<String, PluginHolder> holders,
|
||||
Set<String> visited,
|
||||
Set<String> visiting,
|
||||
List<PluginHolder> order
|
||||
) {
|
||||
String key = holder.name.toLowerCase(Locale.ENGLISH);
|
||||
if (visited.contains(key)) return;
|
||||
if (visiting.contains(key)) {
|
||||
System.err.println("[PatchBukkit] Circular dependency detected involving plugin: " + holder.name);
|
||||
return;
|
||||
}
|
||||
|
||||
visiting.add(key);
|
||||
|
||||
for (String dep : holder.depends) {
|
||||
PluginHolder depHolder = holders.get(dep.toLowerCase(Locale.ENGLISH));
|
||||
if (depHolder != null) {
|
||||
visitHolder(depHolder, holders, visited, visiting, order);
|
||||
}
|
||||
}
|
||||
|
||||
for (String dep : holder.softDepends) {
|
||||
PluginHolder depHolder = holders.get(dep.toLowerCase(Locale.ENGLISH));
|
||||
if (depHolder != null) {
|
||||
visitHolder(depHolder, holders, visited, visiting, order);
|
||||
}
|
||||
}
|
||||
|
||||
visiting.remove(key);
|
||||
visited.add(key);
|
||||
order.add(holder);
|
||||
}
|
||||
|
||||
private static void loadAndRegisterPlugin(PluginHolder holder, Map<String, PluginHolder> allHolders) throws Exception {
|
||||
LinkedHashSet<URL> extraUrls = new LinkedHashSet<>();
|
||||
|
||||
if (!holder.libraries.isEmpty()) {
|
||||
File libsDir = new File(holder.jarFile.getParentFile(), "patchbukkit-libs");
|
||||
if (!libsDir.exists()) libsDir.mkdirs();
|
||||
String libCoords = String.join("\n", holder.libraries);
|
||||
List<File> resolvedLibs = LibraryResolver.resolveLibraries(libCoords, libsDir);
|
||||
for (File lib : resolvedLibs) {
|
||||
if (lib != null && lib.exists()) {
|
||||
extraUrls.add(lib.toURI().toURL());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PatchBukkitPluginClassLoader classLoader = new PatchBukkitPluginClassLoader(
|
||||
PatchBukkitBootstrap.class.getClassLoader(),
|
||||
holder.jarFile,
|
||||
extraUrls.toArray(new URL[0])
|
||||
);
|
||||
|
||||
Class<?> jarClass = Class.forName(holder.mainClass, true, classLoader);
|
||||
Class<? extends Plugin> pluginClass = jarClass.asSubclass(Plugin.class);
|
||||
Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
if (plugin instanceof JavaPlugin javaPlugin) {
|
||||
classLoader.init(javaPlugin);
|
||||
}
|
||||
|
||||
if (Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) {
|
||||
pm.registerPlugin(plugin);
|
||||
}
|
||||
|
||||
try {
|
||||
plugin.onLoad();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("[PatchBukkit] Error during onLoad() for " + holder.name + ": " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
holder.pluginInstance = plugin;
|
||||
|
||||
if (Bukkit.getCommandMap() instanceof PatchBukkitCommandMap commandMap) {
|
||||
Map<String, Map<String, Object>> commands = holder.description.getCommands();
|
||||
if (commands != null) {
|
||||
for (Map.Entry<String, Map<String, Object>> entry : commands.entrySet()) {
|
||||
String cmdName = entry.getKey();
|
||||
Map<String, Object> cmdData = entry.getValue();
|
||||
|
||||
PluginCommand pluginCmd = CommandFactory.create(cmdName, plugin);
|
||||
if (pluginCmd != null) {
|
||||
if (cmdData != null) {
|
||||
if (cmdData.containsKey("description")) {
|
||||
pluginCmd.setDescription(String.valueOf(cmdData.get("description")));
|
||||
}
|
||||
if (cmdData.containsKey("usage")) {
|
||||
pluginCmd.setUsage(String.valueOf(cmdData.get("usage")));
|
||||
}
|
||||
if (cmdData.containsKey("permission")) {
|
||||
pluginCmd.setPermission(String.valueOf(cmdData.get("permission")));
|
||||
}
|
||||
if (cmdData.containsKey("permission-message")) {
|
||||
pluginCmd.setPermissionMessage(String.valueOf(cmdData.get("permission-message")));
|
||||
}
|
||||
if (cmdData.get("aliases") instanceof List<?> aliases) {
|
||||
List<String> strAliases = new ArrayList<>();
|
||||
for (Object a : aliases) {
|
||||
if (a != null) strAliases.add(String.valueOf(a));
|
||||
}
|
||||
pluginCmd.setAliases(strAliases);
|
||||
}
|
||||
}
|
||||
|
||||
commandMap.register(cmdName, holder.name, pluginCmd);
|
||||
System.out.println("[PatchBukkit] Registered command: /" + cmdName + " for plugin " + holder.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PluginHolder {
|
||||
File jarFile;
|
||||
PluginDescriptionFile description;
|
||||
String name;
|
||||
String mainClass;
|
||||
List<String> depends = Collections.emptyList();
|
||||
List<String> softDepends = Collections.emptyList();
|
||||
List<String> provides = Collections.emptyList();
|
||||
List<String> libraries = Collections.emptyList();
|
||||
Plugin pluginInstance;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
package org.patchbukkit.persistence;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataAdapterContext;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PatchBukkitPersistentDataContainer implements PersistentDataContainer {
|
||||
private final Map<NamespacedKey, Object> storage = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public <T, Z> void set(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z value) {
|
||||
storage.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, Z> boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type) {
|
||||
return storage.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(@NotNull NamespacedKey key) {
|
||||
return storage.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, Z> @Nullable Z get(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type) {
|
||||
return (Z) storage.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, Z> @NotNull Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z defaultValue) {
|
||||
Z val = get(key, type);
|
||||
return val != null ? val : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<NamespacedKey> getKeys() {
|
||||
return storage.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull NamespacedKey key) {
|
||||
storage.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return storage.isEmpty();
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(@NotNull PersistentDataContainer other, boolean replace) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PersistentDataAdapterContext getAdapterContext() {
|
||||
return new PersistentDataAdapterContext() {
|
||||
@Override
|
||||
public @NotNull PersistentDataContainer newPersistentDataContainer() {
|
||||
return new PatchBukkitPersistentDataContainer();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromBytes(byte @NotNull [] bytes, boolean clear) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serializeToBytes() {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
@ -11,26 +11,30 @@ import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
|
||||
|
||||
@Deprecated
|
||||
public final class LegacyRegistryIdentifiers {
|
||||
|
||||
public static final Map<Class<?>, RegistryKey<?>> CLASS_TO_KEY_MAP;
|
||||
public static final Map<RegistryKey<?>, Class<?>> KEY_TO_CLASS_MAP;
|
||||
|
||||
static {
|
||||
final ImmutableMap.Builder<Class<?>, RegistryKey<?>> builder = ImmutableMap.builder();
|
||||
final ImmutableMap.Builder<Class<?>, RegistryKey<?>> classToKey = ImmutableMap.builder();
|
||||
final ImmutableMap.Builder<RegistryKey<?>, Class<?>> keyToClass = ImmutableMap.builder();
|
||||
try {
|
||||
for (final Field field : RegistryKey.class.getFields()) {
|
||||
if (field.getType() == RegistryKey.class) {
|
||||
// get the legacy type from the RegistryKey generic parameter on the field
|
||||
final Class<?> legacyType = LegacyRegistryIdentifiers.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]);
|
||||
builder.put(legacyType, (RegistryKey<?>) field.get(null));
|
||||
RegistryKey<?> key = (RegistryKey<?>) field.get(null);
|
||||
classToKey.put(legacyType, key);
|
||||
keyToClass.put(key, legacyType);
|
||||
}
|
||||
}
|
||||
} catch (final ReflectiveOperationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
CLASS_TO_KEY_MAP = builder.build();
|
||||
CLASS_TO_KEY_MAP = classToKey.build();
|
||||
KEY_TO_CLASS_MAP = keyToClass.build();
|
||||
}
|
||||
|
||||
private static Class<?> erase(Type type) {
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
package org.patchbukkit.registry;
|
||||
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockType;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.patchbukkit.PatchBukkitBlockData;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class PatchBukkitBlockType {
|
||||
|
||||
private PatchBukkitBlockType() {}
|
||||
|
||||
public static BlockType create(Material material) {
|
||||
if (material == null || material.isLegacy()) return null;
|
||||
|
||||
return (BlockType) Proxy.newProxyInstance(
|
||||
PatchBukkitBlockType.class.getClassLoader(),
|
||||
new Class<?>[]{BlockType.class},
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("getKey".equals(name) || "key".equals(name)) {
|
||||
return material.getKey();
|
||||
}
|
||||
if ("asMaterial".equals(name)) {
|
||||
return material;
|
||||
}
|
||||
if ("createBlockData".equals(name)) {
|
||||
BlockType self = (BlockType) proxy;
|
||||
if (args == null || args.length == 0) {
|
||||
return PatchBukkitBlockData.newData(material, self, null);
|
||||
} else if (args.length == 1 && args[0] instanceof String s) {
|
||||
return PatchBukkitBlockData.newData(material, self, s);
|
||||
} else if (args.length == 1 && args[0] instanceof Consumer consumer) {
|
||||
BlockData data = PatchBukkitBlockData.newData(material, self, null);
|
||||
consumer.accept(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
if ("createBlockDataStates".equals(name)) {
|
||||
BlockType self = (BlockType) proxy;
|
||||
return Collections.singletonList(PatchBukkitBlockData.newData(material, self, null));
|
||||
}
|
||||
if ("getBlockDataClass".equals(name)) {
|
||||
return BlockData.class;
|
||||
}
|
||||
if ("getItemType".equals(name)) {
|
||||
return material.isItem() ? PatchBukkitItemType.create(material) : null;
|
||||
}
|
||||
if ("hasItemType".equals(name)) {
|
||||
return material.isItem();
|
||||
}
|
||||
if ("isSolid".equals(name)) {
|
||||
return material.isSolid();
|
||||
}
|
||||
if ("isAir".equals(name)) {
|
||||
return material.isAir();
|
||||
}
|
||||
if ("isBurnable".equals(name)) {
|
||||
return material.isBurnable();
|
||||
}
|
||||
if ("isEdible".equals(name)) {
|
||||
return material.isEdible();
|
||||
}
|
||||
if ("isOccluding".equals(name)) {
|
||||
return material.isOccluding();
|
||||
}
|
||||
if ("isInteractable".equals(name)) {
|
||||
return material.isInteractable();
|
||||
}
|
||||
if ("hasGravity".equals(name) || "isGravity".equals(name)) {
|
||||
return material.hasGravity();
|
||||
}
|
||||
if ("translationKey".equals(name) || "getTranslationKey".equals(name)) {
|
||||
return material.getTranslationKey();
|
||||
}
|
||||
if ("typed".equals(name)) {
|
||||
return proxy;
|
||||
}
|
||||
if ("equals".equals(name) && args != null && args.length == 1) {
|
||||
if (args[0] instanceof Keyed k) {
|
||||
return material.getKey().equals(k.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ("hashCode".equals(name)) {
|
||||
return material.getKey().hashCode();
|
||||
}
|
||||
if ("toString".equals(name)) {
|
||||
return "BlockType{" + material.getKey() + "}";
|
||||
}
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == boolean.class) return false;
|
||||
if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0;
|
||||
if (returnType == float.class || returnType == double.class) return 0.0f;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package org.patchbukkit.registry;
|
||||
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ItemType;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
public final class PatchBukkitItemType {
|
||||
|
||||
private PatchBukkitItemType() {}
|
||||
|
||||
public static ItemType create(Material material) {
|
||||
if (material == null) return null;
|
||||
|
||||
return (ItemType) Proxy.newProxyInstance(
|
||||
PatchBukkitItemType.class.getClassLoader(),
|
||||
new Class<?>[]{ItemType.class},
|
||||
(proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
if ("getKey".equals(name) || "key".equals(name)) {
|
||||
return material.getKey();
|
||||
}
|
||||
if ("asMaterial".equals(name)) {
|
||||
return material;
|
||||
}
|
||||
if ("getMaxStackSize".equals(name)) {
|
||||
return material.getMaxStackSize();
|
||||
}
|
||||
if ("getMaxDurability".equals(name)) {
|
||||
return (int) material.getMaxDurability();
|
||||
}
|
||||
if ("isEdible".equals(name)) {
|
||||
return material.isEdible();
|
||||
}
|
||||
if ("isRecord".equals(name)) {
|
||||
return material.isRecord();
|
||||
}
|
||||
if ("createItemStack".equals(name)) {
|
||||
int amount = (args != null && args.length > 0 && args[0] instanceof Integer i) ? i : 1;
|
||||
return new org.patchbukkit.inventory.PatchBukkitItemStack(material, amount);
|
||||
}
|
||||
if ("equals".equals(name) && args != null && args.length == 1) {
|
||||
if (args[0] instanceof Keyed k) {
|
||||
return material.getKey().equals(k.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ("hashCode".equals(name)) {
|
||||
return material.getKey().hashCode();
|
||||
}
|
||||
if ("toString".equals(name)) {
|
||||
return "ItemType{" + material.getKey() + "}";
|
||||
}
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == boolean.class) return false;
|
||||
if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0;
|
||||
if (returnType == float.class || returnType == double.class) return 0.0f;
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,40 @@
|
||||
package org.patchbukkit.registry;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.registry.tag.Tag;
|
||||
import io.papermc.paper.registry.tag.TagKey;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.inventory.ItemType;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.patchbukkit.PatchBukkitLegacy;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
import patchbukkit.registry.GetRegistryDataRequest;
|
||||
import patchbukkit.registry.GetRegistryDataResponse;
|
||||
import patchbukkit.registry.RegistryType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
|
||||
private final Map<NamespacedKey, B> entries = new LinkedHashMap<>();
|
||||
private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>();
|
||||
private final Map<NamespacedKey, B> entries = new ConcurrentHashMap<>();
|
||||
private final Map<String, PatchBukkitTag<B>> tags = new ConcurrentHashMap<>();
|
||||
private final ThreadLocal<Set<NamespacedKey>> inFallback = ThreadLocal.withInitial(HashSet::new);
|
||||
|
||||
private final RegistryKey<B> registryKey;
|
||||
private boolean initialized = false;
|
||||
|
||||
public PatchBukkitRegistry(RegistryKey<B> registryKey) {
|
||||
this(null, null, null, registryKey);
|
||||
}
|
||||
|
||||
public PatchBukkitRegistry(
|
||||
RegistryType registryType,
|
||||
@ -44,22 +51,110 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
RegistryKey<B> registryKey
|
||||
) {
|
||||
this.registryKey = registryKey;
|
||||
if (registryType == null) return;
|
||||
if (registryType != null || extractor != null || factory != null) {
|
||||
initialize(registryType, extractor, factory);
|
||||
}
|
||||
}
|
||||
|
||||
GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder()
|
||||
.setRegistry(registryType)
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void initialize(
|
||||
RegistryType registryType,
|
||||
Function<GetRegistryDataResponse, List<P>> extractor,
|
||||
Function<P, B> factory
|
||||
) {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request);
|
||||
if (response == null) return;
|
||||
|
||||
List<P> protoEntries = extractor.apply(response);
|
||||
for (P protoEntry : protoEntries) {
|
||||
B value = factory.apply(protoEntry);
|
||||
if (value != null) {
|
||||
entries.put(value.getKey(), value);
|
||||
// Auto-discover pre-registered Paper/Bukkit constants for this registry
|
||||
if (registryKey != null) {
|
||||
if (RegistryKey.ITEM.equals(registryKey) || "item".equalsIgnoreCase(registryKey.key().value())) {
|
||||
for (Material mat : Material.values()) {
|
||||
try {
|
||||
if (!mat.isLegacy() && mat.getKey() != null) {
|
||||
B itemType = (B) PatchBukkitItemType.create(mat);
|
||||
if (itemType != null) {
|
||||
entries.put(mat.getKey(), itemType);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} else if (RegistryKey.BLOCK.equals(registryKey) || "block".equalsIgnoreCase(registryKey.key().value())) {
|
||||
for (Material mat : Material.values()) {
|
||||
try {
|
||||
if (!mat.isLegacy() && mat.getKey() != null) {
|
||||
B blockType = (B) PatchBukkitBlockType.create(mat);
|
||||
if (blockType != null) {
|
||||
entries.put(mat.getKey(), blockType);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} else {
|
||||
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
||||
if (valueClass != null) {
|
||||
Map<NamespacedKey, B> discovered = autoDiscover(valueClass);
|
||||
entries.putAll(discovered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
private static <B extends Keyed> Map<NamespacedKey, B> autoDiscover(Class<B> clazz) {
|
||||
Map<NamespacedKey, B> map = new LinkedHashMap<>();
|
||||
if (clazz == null) return map;
|
||||
try {
|
||||
if (clazz.isEnum()) {
|
||||
for (B enumConstant : clazz.getEnumConstants()) {
|
||||
if (enumConstant != null && enumConstant.getKey() != null) {
|
||||
map.put(enumConstant.getKey(), enumConstant);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Field field : clazz.getFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers()) && clazz.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
Object val = field.get(null);
|
||||
if (val != null && clazz.isInstance(val)) {
|
||||
B item = clazz.cast(val);
|
||||
if (item.getKey() != null) {
|
||||
map.put(item.getKey(), item);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,22 +163,72 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
return null;
|
||||
}
|
||||
B value = entries.get(key);
|
||||
if (value == null && (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : ""))) {
|
||||
PatchBukkitSound dynamicSound = new PatchBukkitSound(key.getKey(), entries.size());
|
||||
entries.put(key, (B) dynamicSound);
|
||||
return (B) dynamicSound;
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
|
||||
Set<NamespacedKey> fallbackSet = inFallback.get();
|
||||
if (!fallbackSet.add(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Fallback for SoundEvent
|
||||
if (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) {
|
||||
try {
|
||||
PatchBukkitSound dynamicSound = new PatchBukkitSound(key.getKey(), entries.size());
|
||||
entries.put(key, (B) dynamicSound);
|
||||
return (B) dynamicSound;
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Fallback for ItemType via Material
|
||||
if (RegistryKey.ITEM.equals(registryKey) || "item".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) {
|
||||
try {
|
||||
Material mat = Material.matchMaterial(key.getKey());
|
||||
if (mat != null && mat.isItem()) {
|
||||
B itemType = (B) PatchBukkitItemType.create(mat);
|
||||
if (itemType != null) {
|
||||
entries.put(key, itemType);
|
||||
return itemType;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
// Fallback for BlockType via Material
|
||||
if (RegistryKey.BLOCK.equals(registryKey) || "block".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) {
|
||||
try {
|
||||
Material mat = Material.matchMaterial(key.getKey());
|
||||
if (mat == null) {
|
||||
mat = Material.matchMaterial(key.toString());
|
||||
}
|
||||
if (mat != null && mat.isLegacy()) {
|
||||
mat = PatchBukkitLegacy.fromLegacy(mat);
|
||||
}
|
||||
if (mat != null && !mat.isLegacy()) {
|
||||
B blockType = (B) PatchBukkitBlockType.create(mat);
|
||||
if (blockType != null) {
|
||||
entries.put(key, blockType);
|
||||
return blockType;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} finally {
|
||||
fallbackSet.remove(key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NamespacedKey getKey(B value) {
|
||||
return value.getKey();
|
||||
return value != null ? value.getKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTag(TagKey<B> key) {
|
||||
return tags.containsKey(key.key().asString());
|
||||
return key != null && tags.containsKey(key.key().asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -96,12 +241,7 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
}
|
||||
|
||||
public static <B extends Keyed> PatchBukkitRegistry<Object, B> empty(RegistryKey<B> registryKey) {
|
||||
return new PatchBukkitRegistry<>(
|
||||
null,
|
||||
response -> Collections.emptyList(),
|
||||
obj -> null,
|
||||
registryKey
|
||||
);
|
||||
return new PatchBukkitRegistry<>(registryKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -114,7 +254,6 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
||||
return entries.values().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Stream<NamespacedKey> keyStream() {
|
||||
return entries.keySet().stream();
|
||||
}
|
||||
|
||||
@ -4,20 +4,18 @@ import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.Sound;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import patchbukkit.registry.GetRegistryDataResponse;
|
||||
import patchbukkit.registry.RegistryType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class PatchBukkitRegistryAccess implements RegistryAccess {
|
||||
public class PatchBukkitRegistryAccess extends io.papermc.paper.registry.PaperRegistryAccess {
|
||||
private static final Map<RegistryKey<?>, Registry<?>> instances = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
@ -27,7 +25,7 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
|
||||
private static final Map<RegistryKey<?>, RegistryFactory<?, ?>> FACTORIES = Map.of(
|
||||
RegistryKey.SOUND_EVENT, new RegistryFactory<>(
|
||||
RegistryType.SOUND_EVENT,
|
||||
response -> response.getSoundEvent().getSoundEventsList(),
|
||||
response -> (response != null && response.hasSoundEvent()) ? response.getSoundEvent().getSoundEventsList() : Collections.emptyList(),
|
||||
data -> new PatchBukkitSound(
|
||||
data.getName(),
|
||||
data.getId()
|
||||
@ -41,39 +39,43 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
|
||||
Function<P, B> factory
|
||||
) {}
|
||||
|
||||
@Override
|
||||
public <T extends Keyed> @Nullable Registry<T> getRegistry(Class<T> type) {
|
||||
final RegistryKey<T> registryKey = byType(type);
|
||||
return this.getRegistry(registryKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public <T extends Keyed> Registry<T> getRegistry(RegistryKey<T> registryKey) {
|
||||
if (registryKey == null) return null;
|
||||
|
||||
return (Registry<T>) instances.computeIfAbsent(registryKey, key -> {
|
||||
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(key);
|
||||
if (factoryEntry != null) {
|
||||
return buildRegistry((RegistryKey) key, factoryEntry);
|
||||
}
|
||||
// Return an empty registry for unsupported types
|
||||
return PatchBukkitRegistry.empty((RegistryKey) key);
|
||||
});
|
||||
}
|
||||
Registry<?> existing = instances.get(registryKey);
|
||||
if (existing != null) {
|
||||
return (Registry<T>) existing;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <P, B extends Keyed> PatchBukkitRegistry<P, B> buildRegistry(RegistryKey<B> key, RegistryFactory<P, B> factory) {
|
||||
return new PatchBukkitRegistry<>(
|
||||
factory.registryType(),
|
||||
factory.extractor(),
|
||||
factory.factory(),
|
||||
key
|
||||
);
|
||||
PatchBukkitRegistry 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
public static <T extends Keyed> @Nullable RegistryKey<T> byType(final Class<T> type) {
|
||||
if (type == null) return null;
|
||||
return (RegistryKey<T>) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,19 @@ public class PatchBukkitSound implements Sound {
|
||||
private final int protocolId;
|
||||
|
||||
public PatchBukkitSound(String soundName, int protocolId) {
|
||||
this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName);
|
||||
this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName);
|
||||
if (soundName == null) soundName = "unknown";
|
||||
String keyPath = soundName.startsWith("minecraft:") ? soundName.substring(10) : soundName;
|
||||
keyPath = keyPath.toLowerCase(java.util.Locale.ROOT);
|
||||
|
||||
NamespacedKey key = NamespacedKey.fromString(keyPath);
|
||||
if (key == null) {
|
||||
key = NamespacedKey.minecraft(keyPath.replaceAll("[^a-z0-9/._-]", "_"));
|
||||
}
|
||||
this.namespacedKey = key;
|
||||
this.adventureKey = Key.key(key.namespace(), key.value());
|
||||
this.protocolId = protocolId;
|
||||
this.originalName = soundName;
|
||||
this.enumName = soundName.toUpperCase().replace('.', '_');
|
||||
this.enumName = key.value().toUpperCase(java.util.Locale.ROOT).replace('.', '_').replace('-', '_').replace('/', '_');
|
||||
}
|
||||
|
||||
public String getOriginalName() {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package org.patchbukkit.tag;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Tag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PatchBukkitTag<T extends Keyed> implements Tag<T> {
|
||||
private final NamespacedKey key;
|
||||
private final Set<T> values;
|
||||
|
||||
public PatchBukkitTag(@NotNull NamespacedKey key, @NotNull Set<T> values) {
|
||||
this.key = key;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public PatchBukkitTag(@NotNull NamespacedKey key) {
|
||||
this(key, Collections.emptySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NamespacedKey getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTagged(@NotNull T item) {
|
||||
return values.contains(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<T> getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@ -271,7 +271,6 @@ public class PatchBukkitBlock implements Block {
|
||||
return breakNaturally(tool, triggerEffect, dropExperience, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean breakNaturally(@Nullable ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean dropAsItem) {
|
||||
setType(Material.AIR);
|
||||
return true;
|
||||
@ -395,43 +394,36 @@ public class PatchBukkitBlock implements Block {
|
||||
|
||||
@Override
|
||||
public String translationKey() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'translationKey'");
|
||||
return getType().getTranslationKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getData() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getData'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getLightLevel() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLightLevel'");
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getLightFromSky() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLightFromSky'");
|
||||
return 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getLightFromBlocks() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getLightFromBlocks'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidTool(@NotNull ItemStack tool) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'isValidTool'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Chunk getChunk() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getChunk'");
|
||||
return this.world.getChunkAt(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
package org.patchbukkit.world;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class PatchBukkitChunk implements Chunk {
|
||||
private final World world;
|
||||
private final int x;
|
||||
private final int z;
|
||||
|
||||
public PatchBukkitChunk(World world, int x, int z) {
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getZ() {
|
||||
return this.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull World getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Block getBlock(int x, int y, int z) {
|
||||
return this.world.getBlockAt((this.x << 4) + x, y, (this.z << 4) + z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChunkSnapshot getChunkSnapshot() {
|
||||
return getChunkSnapshot(true, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxBlocky, boolean includeBiome, boolean includeBiomeTempRain) {
|
||||
return getChunkSnapshot(includeMaxBlocky, includeBiome, includeBiomeTempRain, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxBlocky, boolean includeBiome, boolean includeBiomeTempRain, boolean includeBiome3D) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getChunkSnapshot'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntitiesLoaded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entity[] getEntities() {
|
||||
return new Entity[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockState[] getTileEntities() {
|
||||
return new BlockState[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockState[] getTileEntities(boolean useSnapshot) {
|
||||
return new BlockState[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(boolean generate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unload(boolean save) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unload() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSlimeChunk() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceLoaded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceLoaded(boolean progress) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPluginChunkTicket(@NotNull Plugin plugin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removePluginChunkTicket(@NotNull Plugin plugin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<Plugin> getPluginChunkTickets() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInhabitedTime(long ticks) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull BlockData block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<org.bukkit.entity.Player> getPlayersSeeingChunk() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<org.bukkit.generator.structure.GeneratedStructure> getStructures() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<org.bukkit.generator.structure.GeneratedStructure> getStructures(@NotNull org.bukkit.generator.structure.Structure structure) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LoadLevel getLoadLevel() {
|
||||
return LoadLevel.ENTITY_TICKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull org.bukkit.block.Biome biome) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private final org.bukkit.persistence.PersistentDataContainer pdc = new org.patchbukkit.persistence.PatchBukkitPersistentDataContainer();
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() {
|
||||
return this.pdc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<BlockState> getTileEntities(@NotNull java.util.function.Predicate<? super Block> blockPredicate, boolean useSnapshot) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,7 @@ import org.bukkit.util.*;
|
||||
import org.patchbukkit.bridge.BridgeUtils;
|
||||
import patchbukkit.bridge.NativeBridgeFfi;
|
||||
import patchbukkit.world.GetBlockDataRequest;
|
||||
import patchbukkit.world.GetWorldInfoRequest;
|
||||
import patchbukkit.world.SetBlockDataRequest;
|
||||
import patchbukkit.world.SpawnParticleRequest;
|
||||
import org.checkerframework.checker.index.qual.Positive;
|
||||
@ -169,26 +170,17 @@ public class PatchBukkitWorld
|
||||
|
||||
@Override
|
||||
public @NotNull Chunk getChunkAt(int x, int z) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getChunkAt'"
|
||||
);
|
||||
return new PatchBukkitChunk(this, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Chunk getChunkAt(int x, int z, boolean generate) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getChunkAt'"
|
||||
);
|
||||
return new PatchBukkitChunk(this, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Chunk getChunkAt(@NotNull Block block) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getChunkAt'"
|
||||
);
|
||||
return new PatchBukkitChunk(this, block.getX() >> 4, block.getZ() >> 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1203,10 +1195,18 @@ public class PatchBukkitWorld
|
||||
float volume,
|
||||
float pitch
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'playSound'"
|
||||
);
|
||||
if (location == null || sound == null) return;
|
||||
var patchBukkitSound = (org.patchbukkit.registry.PatchBukkitSound) sound;
|
||||
var request = patchbukkit.sound.PlayerPlaySoundRequest.newBuilder()
|
||||
.setLocation(org.patchbukkit.bridge.BridgeUtils.convertLocation(location))
|
||||
.setSound(
|
||||
patchbukkit.sound.Sound.newBuilder()
|
||||
.setCategory(category.name())
|
||||
.setName(patchBukkitSound.getOriginalName())
|
||||
).setVolume(volume)
|
||||
.setPitch(pitch)
|
||||
.build();
|
||||
NativeBridgeFfi.playerPlaySound(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1398,10 +1398,16 @@ public class PatchBukkitWorld
|
||||
@org.jspecify.annotations.Nullable T data,
|
||||
boolean force
|
||||
) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'spawnParticle'"
|
||||
);
|
||||
if (particle == null) return;
|
||||
var request = patchbukkit.world.SpawnParticleRequest.newBuilder()
|
||||
.setWorldUuid(org.patchbukkit.bridge.BridgeUtils.convertUuid(this.getUID()))
|
||||
.setParticle(particle.name().toLowerCase())
|
||||
.setX(x).setY(y).setZ(z)
|
||||
.setCount(count)
|
||||
.setOffsetX(offsetX).setOffsetY(offsetY).setOffsetZ(offsetZ)
|
||||
.setExtra(extra)
|
||||
.build();
|
||||
NativeBridgeFfi.spawnParticle(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1608,26 +1614,42 @@ public class PatchBukkitWorld
|
||||
|
||||
@Override
|
||||
public long getSeed() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getSeed'"
|
||||
);
|
||||
try {
|
||||
var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder()
|
||||
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
||||
.build());
|
||||
return info.getSeed();
|
||||
} catch (Throwable t) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getMinHeight'"
|
||||
);
|
||||
try {
|
||||
var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder()
|
||||
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
||||
.build());
|
||||
return info.getMinHeight();
|
||||
} catch (Throwable t) {
|
||||
return -64;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException(
|
||||
"Unimplemented method 'getMaxHeight'"
|
||||
);
|
||||
try {
|
||||
var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder()
|
||||
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
||||
.build());
|
||||
return info.getMaxHeight();
|
||||
} catch (Throwable t) {
|
||||
return 320;
|
||||
}
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return getMaxHeight() - getMinHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1722,7 +1744,6 @@ public class PatchBukkitWorld
|
||||
@Override
|
||||
public @NotNull List<PoiSearchResult> locateAllPoiInRange(@NotNull Location origin,
|
||||
@NotNull Predicate<PoiType> poiTypePredicate, @Positive int radius, @NotNull Occupancy occupancy) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'locateAllPoiInRange'");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
org.patchbukkit.PatchBukkitServerBuildInfo
|
||||
@ -0,0 +1,63 @@
|
||||
package org.patchbukkit.loader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class LibraryResolverTest {
|
||||
|
||||
@Test
|
||||
public void testLibraryResolverHandlesCustomReposAndCoords(@TempDir File tempDir) {
|
||||
String input = "repo:https://repo.miraculixx.de/snapshots\nde.miraculixx:kpaper:1.1.2";
|
||||
List<File> resolved = LibraryResolver.resolveLibraries(input, tempDir);
|
||||
assertNotNull(resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedJarAndPaperLibrariesExtraction(@TempDir File tempDir) throws Exception {
|
||||
File dummyJar = new File(tempDir, "test-plugin.jar");
|
||||
try (ZipArchiveOutputStreamOrFileOutputStream fos = new ZipArchiveOutputStreamOrFileOutputStream(dummyJar);
|
||||
JarOutputStream jos = new JarOutputStream(fos)) {
|
||||
|
||||
// Add paper-plugin.yml
|
||||
jos.putNextEntry(new JarEntry("paper-plugin.yml"));
|
||||
String paperYml = "name: TestPlugin\nversion: 1.0.0\nmain: org.test.TestMain\nlibraries:\n - \"repo:https://repo.miraculixx.de\"\n - \"de.miraculixx:kpaper:1.1.2\"\n";
|
||||
jos.write(paperYml.getBytes(StandardCharsets.UTF_8));
|
||||
jos.closeEntry();
|
||||
|
||||
// Add paper-libraries.json
|
||||
jos.putNextEntry(new JarEntry("paper-libraries.json"));
|
||||
String paperJson = "{\n \"repositories\": {\n \"miraculixx\": \"https://repo.miraculixx.de\"\n },\n \"dependencies\": [\n \"de.miraculixx:kpaper:1.1.2\"\n ]\n}";
|
||||
jos.write(paperJson.getBytes(StandardCharsets.UTF_8));
|
||||
jos.closeEntry();
|
||||
|
||||
// Add nested jar
|
||||
jos.putNextEntry(new JarEntry("META-INF/jars/nested-lib.jar"));
|
||||
jos.write(new byte[]{0x50, 0x4B, 0x03, 0x04}); // PK header
|
||||
jos.closeEntry();
|
||||
}
|
||||
|
||||
PatchBukkitPluginClassLoader loader = new PatchBukkitPluginClassLoader(
|
||||
LibraryResolverTest.class.getClassLoader(),
|
||||
dummyJar
|
||||
);
|
||||
|
||||
assertNotNull(loader.getDescription());
|
||||
assertEquals("TestPlugin", loader.getDescription().getName());
|
||||
loader.close();
|
||||
}
|
||||
|
||||
private static class ZipArchiveOutputStreamOrFileOutputStream extends FileOutputStream {
|
||||
public ZipArchiveOutputStreamOrFileOutputStream(File file) throws Exception {
|
||||
super(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
package org.patchbukkit.registry;
|
||||
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ItemType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class PatchBukkitRegistryAccessTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
if (org.bukkit.Bukkit.getServer() == null) {
|
||||
org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer();
|
||||
org.bukkit.Bukkit.setServer(server);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerCommandMapReflection() throws Throwable {
|
||||
org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer();
|
||||
|
||||
java.lang.reflect.Field field = org.patchbukkit.PatchBukkitServer.class.getDeclaredField("commandMap");
|
||||
field.setAccessible(true);
|
||||
Object mapObj = field.get(server);
|
||||
assertNotNull(mapObj);
|
||||
assertEquals(server.getCommandMap(), mapObj);
|
||||
assertTrue(mapObj instanceof org.bukkit.command.SimpleCommandMap, "commandMap field must be an instance of SimpleCommandMap for legacy reflection compatibility");
|
||||
assertTrue(server.getCommandMap() instanceof org.bukkit.command.SimpleCommandMap, "getCommandMap() must return an instance of SimpleCommandMap");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchBukkitHelpMapRegistration() {
|
||||
org.patchbukkit.help.PatchBukkitHelpMap helpMap = new org.patchbukkit.help.PatchBukkitHelpMap();
|
||||
assertNotNull(helpMap.getHelpTopics());
|
||||
assertDoesNotThrow(() -> helpMap.registerHelpTopicFactory(Object.class, (cmd) -> null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReentrantRegistryLookup() {
|
||||
PatchBukkitRegistryAccess registryAccess = new PatchBukkitRegistryAccess();
|
||||
|
||||
assertDoesNotThrow(() -> {
|
||||
Registry<?> itemReg = registryAccess.getRegistry(RegistryKey.ITEM);
|
||||
Registry<?> soundReg = registryAccess.getRegistry(RegistryKey.SOUND_EVENT);
|
||||
Registry<?> blockReg = registryAccess.getRegistry(RegistryKey.BLOCK);
|
||||
|
||||
assertNotNull(itemReg);
|
||||
assertNotNull(soundReg);
|
||||
assertNotNull(blockReg);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInspectBlockTypeAndDataMethods() {
|
||||
System.out.println("=== BLOCK TYPE METHODS ===");
|
||||
for (java.lang.reflect.Method m : org.bukkit.block.BlockType.class.getMethods()) {
|
||||
System.out.println("BlockType method: " + m.getName() + " -> " + m.getReturnType().getName() + " args=" + java.util.Arrays.toString(m.getParameterTypes()));
|
||||
}
|
||||
System.out.println("=== BLOCK DATA METHODS ===");
|
||||
for (java.lang.reflect.Method m : org.bukkit.block.data.BlockData.class.getMethods()) {
|
||||
System.out.println("BlockData method: " + m.getName() + " -> " + m.getReturnType().getName() + " args=" + java.util.Arrays.toString(m.getParameterTypes()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockTypeRegistryLookup() {
|
||||
PatchBukkitRegistryAccess registryAccess = new PatchBukkitRegistryAccess();
|
||||
Registry<org.bukkit.block.BlockType> blockReg = registryAccess.getRegistry(RegistryKey.BLOCK);
|
||||
assertNotNull(blockReg);
|
||||
|
||||
org.bukkit.block.BlockType stoneType = blockReg.get(org.bukkit.NamespacedKey.minecraft("stone"));
|
||||
assertNotNull(stoneType, "BlockType for minecraft:stone should be found in BLOCK registry");
|
||||
assertEquals(Material.STONE, stoneType.asMaterial());
|
||||
assertEquals(org.bukkit.NamespacedKey.minecraft("stone"), stoneType.getKey());
|
||||
|
||||
org.bukkit.block.data.BlockData data = stoneType.createBlockData();
|
||||
assertNotNull(data);
|
||||
assertEquals(Material.STONE, data.getMaterial());
|
||||
|
||||
assertNotNull(stoneType.createBlockDataStates(), "createBlockDataStates() must not be null");
|
||||
assertFalse(stoneType.createBlockDataStates().isEmpty(), "createBlockDataStates() must not be empty");
|
||||
assertEquals(org.bukkit.block.data.BlockData.class, stoneType.getBlockDataClass());
|
||||
|
||||
Registry<ItemType> itemReg = registryAccess.getRegistry(RegistryKey.ITEM);
|
||||
ItemType airType = itemReg.get(org.bukkit.NamespacedKey.minecraft("air"));
|
||||
assertNotNull(airType, "ItemType for minecraft:air must be found in ITEM registry");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateBlockDataFromString() {
|
||||
if (org.bukkit.Bukkit.getServer() == null) {
|
||||
org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer();
|
||||
org.bukkit.Bukkit.setServer(server);
|
||||
}
|
||||
|
||||
org.bukkit.block.data.BlockData stoneData = org.bukkit.Bukkit.createBlockData("stone");
|
||||
assertNotNull(stoneData);
|
||||
assertEquals(Material.STONE, stoneData.getMaterial());
|
||||
assertEquals("minecraft:stone", stoneData.getAsString());
|
||||
|
||||
org.bukkit.block.data.BlockData chestData = org.bukkit.Bukkit.createBlockData("minecraft:chest[facing=north]");
|
||||
assertNotNull(chestData);
|
||||
assertEquals(Material.CHEST, chestData.getMaterial());
|
||||
assertTrue(chestData.getAsString().contains("chest"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateBlockDataMaterial() {
|
||||
org.bukkit.block.data.BlockData matData = org.bukkit.Bukkit.createBlockData(Material.STONE);
|
||||
assertNotNull(matData);
|
||||
assertEquals(Material.STONE, matData.getMaterial());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNMSClassAvailability() {
|
||||
assertDoesNotThrow(() -> {
|
||||
try {
|
||||
net.minecraft.SharedConstants.tryDetectVersion();
|
||||
net.minecraft.server.Bootstrap.bootStrap();
|
||||
} catch (Throwable ignored) {}
|
||||
Class<?> nmsBlock = Class.forName("net.minecraft.world.level.block.Block");
|
||||
assertNotNull(nmsBlock, "NMS Block class must be present on classpath");
|
||||
Class<?> nmsRegistries = Class.forName("net.minecraft.core.registries.BuiltInRegistries");
|
||||
assertNotNull(nmsRegistries, "NMS BuiltInRegistries class must be present on classpath");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorldEditAdapterInstantiation() {
|
||||
try {
|
||||
Class<?> pluginCls = Class.forName("com.sk89q.worldedit.bukkit.WorldEditPlugin");
|
||||
assertNotNull(pluginCls, "WorldEditPlugin class must be present");
|
||||
} catch (Throwable t) {
|
||||
System.err.println("WorldEdit Adapter Exception Stacktrace:");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,24 +139,34 @@ public class FfiGenerator {
|
||||
params.add("long " + toCamelCase(method.getName()) + "Addr");
|
||||
}
|
||||
sb.append(params).append(") {\n");
|
||||
sb.append(" try {\n");
|
||||
|
||||
for (MethodDescriptorProto method : methods) {
|
||||
String handleName = toHandleName(method);
|
||||
String addrName = toCamelCase(method.getName()) + "Addr";
|
||||
|
||||
sb.append(" ").append(handleName).append(" = LINKER.downcallHandle(\n");
|
||||
sb.append(" MemorySegment.ofAddress(").append(addrName).append("),\n");
|
||||
sb.append(" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ");
|
||||
sb.append(" ").append(handleName).append(" = LINKER.downcallHandle(\n");
|
||||
sb.append(" MemorySegment.ofAddress(").append(addrName).append("),\n");
|
||||
sb.append(" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ");
|
||||
sb.append("ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));\n");
|
||||
}
|
||||
sb.append(" } catch (Throwable t) {\n");
|
||||
sb.append(" System.err.println(\"[PatchBukkit FFI] Failed init: \" + t.getMessage());\n");
|
||||
sb.append(" t.printStackTrace();\n");
|
||||
sb.append(" }\n");
|
||||
sb.append(" }\n\n");
|
||||
}
|
||||
|
||||
private static void generateInitFreeMethod(StringBuilder sb) {
|
||||
sb.append(" public static void initFree(long freeAddr) {\n");
|
||||
sb.append(" freeNative = LINKER.downcallHandle(\n");
|
||||
sb.append(" MemorySegment.ofAddress(freeAddr),\n");
|
||||
sb.append(" FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));\n");
|
||||
sb.append(" try {\n");
|
||||
sb.append(" freeNative = LINKER.downcallHandle(\n");
|
||||
sb.append(" MemorySegment.ofAddress(freeAddr),\n");
|
||||
sb.append(" FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));\n");
|
||||
sb.append(" } catch (Throwable t) {\n");
|
||||
sb.append(" System.err.println(\"[PatchBukkit FFI] Failed initFree: \" + t.getMessage());\n");
|
||||
sb.append(" t.printStackTrace();\n");
|
||||
sb.append(" }\n");
|
||||
sb.append(" }\n\n");
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,17 @@
|
||||
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.12.1/userguide/multi_project_builds.html in the Gradle documentation.
|
||||
*/
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
name = "papermc"
|
||||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// Apply the foojay-resolver plugin to allow automatic download of JDKs
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
|
||||
@ -4,10 +4,12 @@ package patchbukkit.bridge;
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
import "patchbukkit/abilities.proto";
|
||||
import "patchbukkit/command.proto";
|
||||
import "patchbukkit/common/types.proto";
|
||||
import "patchbukkit/config.proto";
|
||||
import "patchbukkit/entity.proto";
|
||||
import "patchbukkit/events/event.proto";
|
||||
import "patchbukkit/itemstack.proto";
|
||||
import "patchbukkit/log.proto";
|
||||
import "patchbukkit/message.proto";
|
||||
import "patchbukkit/registry.proto";
|
||||
@ -27,6 +29,15 @@ service NativeBridge {
|
||||
rpc TeleportEntity(patchbukkit.entity.TeleportEntityRequest) returns (google.protobuf.Empty);
|
||||
rpc GetGamemode(patchbukkit.common.UUID) returns (patchbukkit.entity.GetGamemodeResponse);
|
||||
rpc SetGamemode(patchbukkit.entity.SetGamemodeRequest) returns (google.protobuf.Empty);
|
||||
rpc IsOnGround(patchbukkit.common.UUID) returns (patchbukkit.entity.IsOnGroundResponse);
|
||||
rpc GetPlayerLocale(patchbukkit.common.UUID) returns (patchbukkit.entity.GetPlayerLocaleResponse);
|
||||
rpc IsOp(patchbukkit.common.UUID) returns (patchbukkit.entity.IsOpResponse);
|
||||
|
||||
rpc GetPlayerInventory(patchbukkit.common.UUID) returns (patchbukkit.itemstack.GetPlayerInventoryResponse);
|
||||
rpc SetPlayerInventorySlot(patchbukkit.itemstack.SetPlayerInventorySlotRequest) returns (google.protobuf.Empty);
|
||||
rpc SetPlayerSelectedSlot(patchbukkit.itemstack.SetPlayerSelectedSlotRequest) returns (google.protobuf.Empty);
|
||||
rpc SetPlayerEquipment(patchbukkit.itemstack.SetPlayerEquipmentRequest) returns (google.protobuf.Empty);
|
||||
rpc ClearPlayerInventory(patchbukkit.common.UUID) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetBlockData(patchbukkit.world.GetBlockDataRequest) returns (patchbukkit.world.GetBlockDataResponse);
|
||||
rpc SetBlockData(patchbukkit.world.SetBlockDataRequest) returns (google.protobuf.Empty);
|
||||
@ -50,4 +61,9 @@ service NativeBridge {
|
||||
rpc SendLog(patchbukkit.log.SendLogRequest) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetPatchBukkitConfig(patchbukkit.common.EmptyRequest) returns (patchbukkit.config.GetPatchBukkitConfigResponse);
|
||||
|
||||
rpc RegisterCommand(patchbukkit.command.RegisterCommandRequest) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetWorldInfo(patchbukkit.world.GetWorldInfoRequest) returns (patchbukkit.world.GetWorldInfoResponse);
|
||||
}
|
||||
|
||||
|
||||
13
proto/patchbukkit/command.proto
Normal file
13
proto/patchbukkit/command.proto
Normal file
@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package patchbukkit.command;
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "patchbukkit.command";
|
||||
|
||||
message RegisterCommandRequest {
|
||||
string cmd_name = 1;
|
||||
repeated string aliases = 2;
|
||||
string description = 3;
|
||||
string plugin_name = 4;
|
||||
}
|
||||
@ -54,5 +54,18 @@ message SetGamemodeRequest {
|
||||
int32 gamemode = 2;
|
||||
}
|
||||
|
||||
message IsOnGroundResponse {
|
||||
bool on_ground = 1;
|
||||
}
|
||||
|
||||
message GetPlayerLocaleResponse {
|
||||
string locale = 1;
|
||||
}
|
||||
|
||||
message IsOpResponse {
|
||||
bool is_op = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
72
proto/patchbukkit/events/block_events.proto
Normal file
72
proto/patchbukkit/events/block_events.proto
Normal file
@ -0,0 +1,72 @@
|
||||
syntax = "proto3";
|
||||
package patchbukkit.events;
|
||||
|
||||
import "patchbukkit/common/types.proto";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "patchbukkit.events";
|
||||
|
||||
message BlockBreakEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
int32 block_x = 2;
|
||||
int32 block_y = 3;
|
||||
int32 block_z = 4;
|
||||
string block_type = 5;
|
||||
uint32 exp = 6;
|
||||
bool drop_items = 7;
|
||||
}
|
||||
|
||||
message BlockPlaceEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
int32 block_x = 2;
|
||||
int32 block_y = 3;
|
||||
int32 block_z = 4;
|
||||
string block_placed_type = 5;
|
||||
string block_against_type = 6;
|
||||
bool can_build = 7;
|
||||
}
|
||||
|
||||
message SignChangeEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
int32 block_x = 2;
|
||||
int32 block_y = 3;
|
||||
int32 block_z = 4;
|
||||
repeated string lines = 5;
|
||||
}
|
||||
|
||||
message BlockDamageEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
int32 block_x = 2;
|
||||
int32 block_y = 3;
|
||||
int32 block_z = 4;
|
||||
bool insta_break = 5;
|
||||
}
|
||||
|
||||
message BlockIgniteEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
int32 block_x = 2;
|
||||
int32 block_y = 3;
|
||||
int32 block_z = 4;
|
||||
string ignite_cause = 5;
|
||||
}
|
||||
|
||||
message BlockGrowEvent {
|
||||
int32 block_x = 1;
|
||||
int32 block_y = 2;
|
||||
int32 block_z = 3;
|
||||
string new_block_type = 4;
|
||||
}
|
||||
|
||||
message BlockFormEvent {
|
||||
int32 block_x = 1;
|
||||
int32 block_y = 2;
|
||||
int32 block_z = 3;
|
||||
string new_block_type = 4;
|
||||
}
|
||||
|
||||
message BlockFadeEvent {
|
||||
int32 block_x = 1;
|
||||
int32 block_y = 2;
|
||||
int32 block_z = 3;
|
||||
string new_block_type = 4;
|
||||
}
|
||||
@ -5,6 +5,7 @@ package patchbukkit.events;
|
||||
import "patchbukkit/common/types.proto";
|
||||
import "patchbukkit/events/player_events.proto";
|
||||
import "patchbukkit/events/server_events.proto";
|
||||
import "patchbukkit/events/block_events.proto";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "patchbukkit.events";
|
||||
@ -17,6 +18,21 @@ message Event {
|
||||
PlayerQuitEvent player_quit = 4;
|
||||
PlayerInteractEvent player_interact = 5;
|
||||
PlayerGameModeChangeEvent player_gamemode_change = 6;
|
||||
BlockBreakEvent block_break = 7;
|
||||
BlockPlaceEvent block_place = 8;
|
||||
PlayerToggleSneakEvent player_toggle_sneak = 9;
|
||||
PlayerToggleSprintEvent player_toggle_sprint = 10;
|
||||
PlayerToggleFlightEvent player_toggle_flight = 11;
|
||||
PlayerChatEvent player_chat = 12;
|
||||
PlayerMoveEvent player_move = 13;
|
||||
PlayerInteractEntityEvent player_interact_entity = 14;
|
||||
SignChangeEvent sign_change = 15;
|
||||
BlockDamageEvent block_damage = 16;
|
||||
ServerCommandEvent server_command = 17;
|
||||
BlockIgniteEvent block_ignite = 18;
|
||||
BlockGrowEvent block_grow = 19;
|
||||
BlockFormEvent block_form = 20;
|
||||
BlockFadeEvent block_fade = 21;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,3 +31,38 @@ message PlayerGameModeChangeEvent {
|
||||
string new_gamemode = 2;
|
||||
}
|
||||
|
||||
message PlayerToggleSneakEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
bool is_sneaking = 2;
|
||||
}
|
||||
|
||||
message PlayerToggleSprintEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
bool is_sprinting = 2;
|
||||
}
|
||||
|
||||
message PlayerToggleFlightEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
bool is_flying = 2;
|
||||
}
|
||||
|
||||
message PlayerChatEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
string message = 2;
|
||||
string format = 3;
|
||||
}
|
||||
|
||||
message PlayerMoveEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
patchbukkit.common.Location from = 2;
|
||||
patchbukkit.common.Location to = 3;
|
||||
}
|
||||
|
||||
message PlayerInteractEntityEvent {
|
||||
patchbukkit.common.UUID player_uuid = 1;
|
||||
patchbukkit.common.UUID target_uuid = 2;
|
||||
string action = 3;
|
||||
bool is_sneaking = 4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -13,3 +13,8 @@ message PluginEnableEvent {
|
||||
message PluginDisableEvent {
|
||||
string plugin_name = 1;
|
||||
}
|
||||
|
||||
message ServerCommandEvent {
|
||||
string sender_name = 1;
|
||||
string command = 2;
|
||||
}
|
||||
|
||||
@ -16,3 +16,31 @@ message ItemStack {
|
||||
string type = 1;
|
||||
uint32 amount = 2;
|
||||
}
|
||||
|
||||
message GetPlayerInventoryResponse {
|
||||
repeated ItemStack main_inventory = 1;
|
||||
uint32 selected_slot = 2;
|
||||
ItemStack off_hand = 3;
|
||||
ItemStack helmet = 4;
|
||||
ItemStack chestplate = 5;
|
||||
ItemStack leggings = 6;
|
||||
ItemStack boots = 7;
|
||||
}
|
||||
|
||||
message SetPlayerInventorySlotRequest {
|
||||
patchbukkit.common.UUID uuid = 1;
|
||||
uint32 slot = 2;
|
||||
ItemStack item = 3;
|
||||
}
|
||||
|
||||
message SetPlayerSelectedSlotRequest {
|
||||
patchbukkit.common.UUID uuid = 1;
|
||||
uint32 slot = 2;
|
||||
}
|
||||
|
||||
message SetPlayerEquipmentRequest {
|
||||
patchbukkit.common.UUID uuid = 1;
|
||||
uint32 slot_type = 2;
|
||||
ItemStack item = 3;
|
||||
}
|
||||
|
||||
|
||||
@ -66,4 +66,15 @@ message SetWorldBorderRequest {
|
||||
WorldBorderData border = 2;
|
||||
}
|
||||
|
||||
message GetWorldInfoRequest {
|
||||
patchbukkit.common.UUID world_uuid = 1;
|
||||
}
|
||||
|
||||
message GetWorldInfoResponse {
|
||||
int32 min_height = 1;
|
||||
int32 max_height = 2;
|
||||
int32 height = 3;
|
||||
int64 seed = 4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1817
rust/Cargo.lock
generated
1817
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,7 @@ uuid = "1.24.0"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-saphyr = "0.0.29"
|
||||
serde-saphyr = "1.0.1"
|
||||
|
||||
rand = { git = "https://github.com/rust-random/rand"}
|
||||
prost = "0.14"
|
||||
|
||||
@ -40,6 +40,9 @@ pub fn setup_java(base: PathBuf) {
|
||||
.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"),
|
||||
]);
|
||||
|
||||
@ -59,10 +62,6 @@ pub fn setup_java(base: PathBuf) {
|
||||
}
|
||||
|
||||
fs::create_dir_all(&jassets).unwrap();
|
||||
let dest_jar = jassets.join("patchbukkit.jar");
|
||||
fs::copy(&patchbukkit_jar, &dest_jar)
|
||||
.map_err(|err| format!("Failed to copy patchbukkit.jar to jassets: {err:?}"))
|
||||
.unwrap();
|
||||
|
||||
let cdylib = std::env::var("CARGO_CDYLIB_FILE_J4RS").unwrap();
|
||||
let cdylib = PathBuf::from(cdylib);
|
||||
@ -71,10 +70,16 @@ pub fn setup_java(base: PathBuf) {
|
||||
fs::create_dir_all(&cdylib_to).unwrap();
|
||||
|
||||
let original_name = cdylib.file_name().unwrap().to_string_lossy();
|
||||
let stem = original_name.split('-').next().unwrap(); // before the first '-'
|
||||
let ext = cdylib.extension().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(format!("{stem}.{ext}"));
|
||||
cdylib_to.push(target_name);
|
||||
|
||||
fs::copy(&cdylib, &cdylib_to)
|
||||
.map_err(|err| format!("Failed to copy j4rs native lib: {err:?}"))
|
||||
|
||||
@ -224,7 +224,7 @@ pub fn initialize_ffi_callbacks(jvm: &j4rs::Jvm) -> anyhow::Result<()> {{
|
||||
file,
|
||||
r#"
|
||||
// Initialize {}
|
||||
jvm.invoke_static(
|
||||
if let Err(e) = jvm.invoke_static(
|
||||
"{}",
|
||||
"init",
|
||||
&["#,
|
||||
@ -242,13 +242,17 @@ pub fn initialize_ffi_callbacks(jvm: &j4rs::Jvm) -> anyhow::Result<()> {{
|
||||
writeln!(
|
||||
file,
|
||||
r#" ],
|
||||
)?;
|
||||
) {{
|
||||
tracing::error!("Failed to initialize FFI service: {{:?}}", e);
|
||||
}}
|
||||
|
||||
jvm.invoke_static(
|
||||
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);
|
||||
}}"#,
|
||||
service.java_class
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2026-07-31"
|
||||
channel = "nightly-2026-08-07"
|
||||
components = ["rust-analyzer", "rust-src", "rustfmt", "clippy"]
|
||||
|
||||
|
||||
@ -131,8 +131,20 @@ impl CommandExecutor for JavaCommandExecutor {
|
||||
) -> pumpkin::command::CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let full_command = match args.get(ARG_ANY) {
|
||||
Some(Arg::Msg(msg)) => format!("/{} {}", self.cmd_name, msg),
|
||||
_ => format!("/{}", self.cmd_name),
|
||||
Some(Arg::Msg(msg)) => {
|
||||
if self.cmd_name.starts_with('/') {
|
||||
format!("{} {}", self.cmd_name, msg)
|
||||
} else {
|
||||
format!("/{} {}", self.cmd_name, msg)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.cmd_name.starts_with('/') {
|
||||
self.cmd_name.clone()
|
||||
} else {
|
||||
format!("/{}", self.cmd_name)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, _rx) = oneshot::channel();
|
||||
|
||||
@ -184,6 +184,14 @@ pub struct PaperPluginYml {
|
||||
/// Other plugins this plugin can substitute for
|
||||
#[serde(default)]
|
||||
pub provides: Option<Vec<String>>,
|
||||
|
||||
/// Libraries declared for runtime resolution
|
||||
#[serde(default)]
|
||||
pub libraries: Option<Vec<String>>,
|
||||
|
||||
/// Repositories declared for runtime resolution
|
||||
#[serde(default)]
|
||||
pub repositories: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl PaperPluginYml {
|
||||
|
||||
@ -29,6 +29,20 @@ pub fn setup_directories(server: &Context) -> Result<PatchBukkitDirectories, Str
|
||||
fs::create_dir_all(&plugins)
|
||||
.map_err(|err| format!("Failed to create patchbukkit-plugins folder: {err:?}"))?;
|
||||
|
||||
let patchbukkit_jar_dest = jassets.join("patchbukkit.jar");
|
||||
|
||||
let candidates = [
|
||||
server_root.join("java").join("patchbukkit").join("build").join("libs").join("patchbukkit.jar"),
|
||||
server_root.join("patchbukkit").join("java").join("patchbukkit").join("build").join("libs").join("patchbukkit.jar"),
|
||||
];
|
||||
|
||||
for candidate in &candidates {
|
||||
if candidate.exists() {
|
||||
let _ = fs::copy(candidate, &patchbukkit_jar_dest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PatchBukkitDirectories {
|
||||
base,
|
||||
plugins,
|
||||
|
||||
@ -11,7 +11,13 @@ use tokio::sync::{mpsc, oneshot};
|
||||
use crate::java::jvm::commands::JvmCommand;
|
||||
use crate::proto::patchbukkit::common::Uuid;
|
||||
use crate::proto::patchbukkit::events::event::Data;
|
||||
use crate::proto::patchbukkit::events::{Event, PlayerJoinEvent};
|
||||
use crate::proto::patchbukkit::events::{
|
||||
BlockBreakEvent, BlockDamageEvent, BlockFadeEvent, BlockFormEvent, BlockGrowEvent,
|
||||
BlockIgniteEvent, BlockPlaceEvent, Event, PlayerChatEvent, PlayerGameModeChangeEvent,
|
||||
PlayerInteractEntityEvent, PlayerInteractEvent, PlayerJoinEvent, PlayerMoveEvent,
|
||||
PlayerQuitEvent, PlayerToggleFlightEvent, PlayerToggleSneakEvent, PlayerToggleSprintEvent,
|
||||
ServerCommandEvent, SignChangeEvent,
|
||||
};
|
||||
|
||||
pub struct EventContext {
|
||||
pub server: Arc<Server>,
|
||||
@ -36,7 +42,7 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
join_message: serde_json::to_string(&self.join_message).unwrap(),
|
||||
join_message: serde_json::to_string(&self.join_message).unwrap_or_default(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
@ -56,6 +62,531 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::player::player_leave::PlayerLeaveEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerQuit(PlayerQuitEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
quit_message: serde_json::to_string(&self.leave_message).unwrap_or_default(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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()?)?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent
|
||||
for pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent
|
||||
{
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerGamemodeChange(PlayerGameModeChangeEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
new_gamemode: format!("{:?}", self.new_gamemode),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::player::player_interact_event::PlayerInteractEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
let (x, y, z) = match self.clicked_pos {
|
||||
Some(pos) => (pos.0.x, pos.0.y, pos.0.z),
|
||||
None => (0, 0, 0),
|
||||
};
|
||||
let action_str = match self.action {
|
||||
pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickBlock => {
|
||||
"LEFT_CLICK_BLOCK"
|
||||
}
|
||||
pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickAir => {
|
||||
"LEFT_CLICK_AIR"
|
||||
}
|
||||
pumpkin::plugin::player::player_interact_event::InteractAction::RightClickAir => {
|
||||
"RIGHT_CLICK_AIR"
|
||||
}
|
||||
pumpkin::plugin::player::player_interact_event::InteractAction::RightClickBlock => {
|
||||
"RIGHT_CLICK_BLOCK"
|
||||
}
|
||||
};
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerInteract(PlayerInteractEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
action: action_str.to_string(),
|
||||
clicked_x: x,
|
||||
clicked_y: y,
|
||||
clicked_z: z,
|
||||
block_face: "SELF".to_string(),
|
||||
hand: "HAND".to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_break::BlockBreakEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockBreak(BlockBreakEvent {
|
||||
player_uuid: self.player.as_ref().map(|p| Uuid {
|
||||
value: p.gameprofile.id.to_string(),
|
||||
}),
|
||||
block_x: self.block_position.0.x,
|
||||
block_y: self.block_position.0.y,
|
||||
block_z: self.block_position.0.z,
|
||||
block_type: format!("{:?}", self.block),
|
||||
exp: self.exp,
|
||||
drop_items: self.drop,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: self.player.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_place::BlockPlaceEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockPlace(BlockPlaceEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
block_x: self.block_position.0.x,
|
||||
block_y: self.block_position.0.y,
|
||||
block_z: self.block_position.0.z,
|
||||
block_placed_type: format!("{:?}", self.block_placed),
|
||||
block_against_type: format!("{:?}", self.block_placed_against),
|
||||
can_build: self.can_build,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent
|
||||
for pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent
|
||||
{
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerToggleSneak(PlayerToggleSneakEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
is_sneaking: self.is_sneaking,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent
|
||||
for pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent
|
||||
{
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerToggleSprint(PlayerToggleSprintEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
is_sprinting: self.is_sprinting,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent
|
||||
for pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent
|
||||
{
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerToggleFlight(PlayerToggleFlightEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
is_flying: self.is_flying,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::player::player_move::PlayerMoveEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
let move_vel = self.to - self.from;
|
||||
self.player.living_entity.entity.velocity.store(move_vel);
|
||||
|
||||
let player_uuid = self.player.gameprofile.id.to_string();
|
||||
let world_uuid = self.player.world().uuid.to_string();
|
||||
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerMove(PlayerMoveEvent {
|
||||
player_uuid: Some(Uuid { value: player_uuid }),
|
||||
from: Some(crate::proto::patchbukkit::common::Location {
|
||||
world: Some(crate::proto::patchbukkit::common::World {
|
||||
uuid: Some(Uuid {
|
||||
value: world_uuid.clone(),
|
||||
}),
|
||||
}),
|
||||
position: Some(crate::proto::patchbukkit::common::Vec3 {
|
||||
x: self.from.x,
|
||||
y: self.from.y,
|
||||
z: self.from.z,
|
||||
}),
|
||||
yaw: self.player.living_entity.entity.yaw.load(),
|
||||
pitch: self.player.living_entity.entity.pitch.load(),
|
||||
}),
|
||||
to: Some(crate::proto::patchbukkit::common::Location {
|
||||
world: Some(crate::proto::patchbukkit::common::World {
|
||||
uuid: Some(Uuid { value: world_uuid }),
|
||||
}),
|
||||
position: Some(crate::proto::patchbukkit::common::Vec3 {
|
||||
x: self.to.x,
|
||||
y: self.to.y,
|
||||
z: self.to.z,
|
||||
}),
|
||||
yaw: self.player.living_entity.entity.yaw.load(),
|
||||
pitch: self.player.living_entity.entity.pitch.load(),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::PlayerMove(event) = data
|
||||
&& let Some(to) = event.to
|
||||
&& let Some(pos) = to.position
|
||||
{
|
||||
self.to = pumpkin_util::math::vector3::Vector3::new(pos.x, pos.y, pos.z);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::player::player_chat::PlayerChatEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerChat(PlayerChatEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
message: self.message.clone(),
|
||||
format: "<%1$s> %2$s".to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::PlayerChat(event) = data {
|
||||
self.message = event.message;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent
|
||||
for pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent
|
||||
{
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
let action_str = match self.action {
|
||||
pumpkin_protocol::java::server::play::ActionType::Interact => "INTERACT",
|
||||
pumpkin_protocol::java::server::play::ActionType::Attack => "ATTACK",
|
||||
pumpkin_protocol::java::server::play::ActionType::InteractAt => "INTERACT_AT",
|
||||
};
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::PlayerInteractEntity(PlayerInteractEntityEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
target_uuid: Some(Uuid {
|
||||
value: self.target.get_entity().entity_uuid.to_string(),
|
||||
}),
|
||||
action: action_str.to_string(),
|
||||
is_sneaking: self.sneaking,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::sign_change::SignChangeEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::SignChange(SignChangeEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
lines: self.lines.clone(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::SignChange(event) = data {
|
||||
self.lines = event.lines;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_damage::BlockDamageEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockDamage(BlockDamageEvent {
|
||||
player_uuid: Some(Uuid {
|
||||
value: self.player.gameprofile.id.to_string(),
|
||||
}),
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
insta_break: false,
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: Some(self.player.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::server::server_command::ServerCommandEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::ServerCommand(ServerCommandEvent {
|
||||
sender_name: "Console".to_string(),
|
||||
command: self.command.clone(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, data: Data) -> Option<()> {
|
||||
if let Data::ServerCommand(event) = data {
|
||||
self.command = event.command;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_ignite::BlockIgniteEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
let player_uuid = self.player.as_ref().map(|p| Uuid {
|
||||
value: p.gameprofile.id.to_string(),
|
||||
});
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockIgnite(BlockIgniteEvent {
|
||||
player_uuid,
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
ignite_cause: "FLINT_AND_STEEL".to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: self.player.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_grow::BlockGrowEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockGrow(BlockGrowEvent {
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
new_block_type: self.new_block.name.to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_form::BlockFormEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockForm(BlockFormEvent {
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
new_block_type: self.block.name.to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::block::block_fade::BlockFadeEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> JvmEventPayload {
|
||||
JvmEventPayload {
|
||||
event: Event {
|
||||
data: Some(Data::BlockFade(BlockFadeEvent {
|
||||
block_x: self.block_pos.0.x,
|
||||
block_y: self.block_pos.0.y,
|
||||
block_z: self.block_pos.0.z,
|
||||
new_block_type: self.block.name.to_string(),
|
||||
})),
|
||||
},
|
||||
context: EventContext {
|
||||
server,
|
||||
player: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifications(&mut self, _server: &Arc<Server>, _data: Data) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PatchBukkitEventHandler<E: PatchBukkitEvent> {
|
||||
plugin_name: String,
|
||||
command_tx: mpsc::Sender<JvmCommand>,
|
||||
|
||||
@ -16,12 +16,17 @@ pub fn discover_jar_files(plugin_folder: &Path) -> impl Iterator<Item = PathBuf>
|
||||
glob(&pattern)
|
||||
.expect("Invalid glob pattern")
|
||||
.filter_map(|entry| {
|
||||
entry
|
||||
let path = entry
|
||||
.map_err(|e| tracing::error!("Glob error: {e:?}"))
|
||||
.ok()?
|
||||
.canonicalize()
|
||||
.map_err(|e| tracing::error!("Canonicalize error: {e:?}"))
|
||||
.ok()
|
||||
.ok()?;
|
||||
if path.components().any(|c| c.as_os_str() == "patchbukkit-libs") {
|
||||
None
|
||||
} else {
|
||||
Some(path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,3 +56,126 @@ pub fn read_configs_from_jar<P: AsRef<Path>>(
|
||||
|
||||
Ok((paper_plugin_yml, spigot_plugin_yml))
|
||||
}
|
||||
|
||||
pub fn read_libraries_from_jar<P: AsRef<Path>>(jar_path: P) -> Vec<String> {
|
||||
let mut libraries = Vec::new();
|
||||
let file = match File::open(jar_path.as_ref()) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return libraries,
|
||||
};
|
||||
let mut archive = match ZipArchive::new(file) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return libraries,
|
||||
};
|
||||
|
||||
// 1. paper-libraries.json
|
||||
if let Ok(mut file) = archive.by_name("paper-libraries.json") {
|
||||
let mut content = String::new();
|
||||
if file.read_to_string(&mut content).is_ok() {
|
||||
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(repos) = val.get("repositories") {
|
||||
if let Some(map) = repos.as_object() {
|
||||
for (_, v) in map {
|
||||
if let Some(url) = v.as_str() {
|
||||
let repo_entry = format!("repo:{url}");
|
||||
if !libraries.contains(&repo_entry) {
|
||||
libraries.push(repo_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(arr) = repos.as_array() {
|
||||
for item in arr {
|
||||
if let Some(url) = item.as_str() {
|
||||
let repo_entry = format!("repo:{url}");
|
||||
if !libraries.contains(&repo_entry) {
|
||||
libraries.push(repo_entry);
|
||||
}
|
||||
} else if let Some(obj) = item.as_object() {
|
||||
if let Some(url) = obj.get("url").and_then(|u| u.as_str()) {
|
||||
let repo_entry = format!("repo:{url}");
|
||||
if !libraries.contains(&repo_entry) {
|
||||
libraries.push(repo_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deps) = val.get("dependencies") {
|
||||
if let Some(arr) = deps.as_array() {
|
||||
for item in arr {
|
||||
if let Some(s) = item.as_str() {
|
||||
if !libraries.contains(&s.to_string()) {
|
||||
libraries.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(map) = deps.as_object() {
|
||||
for (k, v) in map {
|
||||
if let Some(ver) = v.as_str() {
|
||||
let coord = format!("{k}:{ver}");
|
||||
if !libraries.contains(&coord) {
|
||||
libraries.push(coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. paper-libraries.list
|
||||
if let Ok(mut file) = archive.by_name("paper-libraries.list") {
|
||||
let mut content = String::new();
|
||||
if file.read_to_string(&mut content).is_ok() {
|
||||
for line in content.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() && !trimmed.starts_with('#') {
|
||||
if !libraries.contains(&trimmed.to_string()) {
|
||||
libraries.push(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libraries
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
#[test]
|
||||
fn test_read_libraries_from_jar_json() {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let jar_path = temp_dir.join("test_libraries.jar");
|
||||
|
||||
{
|
||||
let file = File::create(&jar_path).unwrap();
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
|
||||
zip.start_file("paper-libraries.json", SimpleFileOptions::default()).unwrap();
|
||||
let json = r#"{
|
||||
"repositories": {
|
||||
"miraculixx": "https://repo.miraculixx.de"
|
||||
},
|
||||
"dependencies": [
|
||||
"de.miraculixx:kpaper:1.1.2"
|
||||
]
|
||||
}"#;
|
||||
zip.write_all(json.as_bytes()).unwrap();
|
||||
zip.finish().unwrap();
|
||||
}
|
||||
|
||||
let libs = read_libraries_from_jar(&jar_path);
|
||||
assert!(libs.contains(&"repo:https://repo.miraculixx.de".to_string()));
|
||||
assert!(libs.contains(&"de.miraculixx:kpaper:1.1.2".to_string()));
|
||||
|
||||
let _ = std::fs::remove_file(jar_path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ pub enum JvmCommand {
|
||||
respond_to: oneshot::Sender<LoadPluginResult>,
|
||||
},
|
||||
InstantiateAllPlugins {
|
||||
plugins_dir: PathBuf,
|
||||
respond_to: oneshot::Sender<Result<()>>,
|
||||
server: Arc<Context>,
|
||||
command_tx: mpsc::Sender<Self>,
|
||||
|
||||
@ -100,6 +100,7 @@ impl JvmWorker {
|
||||
};
|
||||
}
|
||||
JvmCommand::InstantiateAllPlugins {
|
||||
plugins_dir,
|
||||
respond_to,
|
||||
server,
|
||||
command_tx,
|
||||
@ -113,6 +114,7 @@ impl JvmWorker {
|
||||
self.plugin_manager
|
||||
.instantiate_all_plugins(
|
||||
jvm,
|
||||
&plugins_dir,
|
||||
&server,
|
||||
command_tx,
|
||||
&mut self.command_manager,
|
||||
@ -216,7 +218,6 @@ impl JvmWorker {
|
||||
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));
|
||||
@ -227,6 +228,9 @@ impl JvmWorker {
|
||||
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"),
|
||||
]);
|
||||
|
||||
@ -248,13 +252,10 @@ impl JvmWorker {
|
||||
}
|
||||
|
||||
pub fn setup_patchbukkit_server(jvm: &Jvm) -> anyhow::Result<()> {
|
||||
let patchbukkit_server =
|
||||
jvm.create_instance("org.patchbukkit.PatchBukkitServer", InvocationArg::empty())?;
|
||||
|
||||
jvm.invoke_static(
|
||||
"org.bukkit.Bukkit",
|
||||
"setServer",
|
||||
&[InvocationArg::from(patchbukkit_server)],
|
||||
"org.patchbukkit.PatchBukkitServer",
|
||||
"initServer",
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
65
rust/src/java/native_callbacks/command.rs
Normal file
65
rust/src/java/native_callbacks/command.rs
Normal file
@ -0,0 +1,65 @@
|
||||
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 cmd_name = request.cmd_name;
|
||||
let aliases = request.aliases;
|
||||
let description = request.description;
|
||||
let plugin_name = request.plugin_name;
|
||||
let command_tx = context.command_tx.clone();
|
||||
let plugin_context = context.plugin_context.clone();
|
||||
|
||||
context.runtime.spawn(async move {
|
||||
let mut raw_names: Vec<String> = vec![cmd_name.clone()];
|
||||
raw_names.extend(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 node = init_java_command(
|
||||
cmd_name.clone(),
|
||||
command_tx,
|
||||
names,
|
||||
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.write().await.register_permission(Permission::new(
|
||||
&permission,
|
||||
&format!("Allows running command `{cmd_name}` from `{plugin_name}`"),
|
||||
PermissionDefault::Allow,
|
||||
));
|
||||
|
||||
plugin_context.register_command(node, permission).await;
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
@ -119,3 +119,64 @@ pub fn ffi_native_bridge_teleport_entity_impl(request: TeleportEntityRequest) ->
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_is_on_ground_impl(
|
||||
request: Uuid,
|
||||
) -> Option<crate::proto::patchbukkit::entity::IsOnGroundResponse> {
|
||||
with_player(Some(&request), |player| {
|
||||
let on_ground = player
|
||||
.living_entity
|
||||
.entity
|
||||
.on_ground
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
crate::proto::patchbukkit::entity::IsOnGroundResponse { on_ground }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_get_player_locale_impl(
|
||||
request: Uuid,
|
||||
) -> Option<crate::proto::patchbukkit::entity::GetPlayerLocaleResponse> {
|
||||
with_player(Some(&request), |player| {
|
||||
let config = player.config.load();
|
||||
crate::proto::patchbukkit::entity::GetPlayerLocaleResponse {
|
||||
locale: config.locale.clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_is_op_impl(
|
||||
request: Uuid,
|
||||
) -> Option<crate::proto::patchbukkit::entity::IsOpResponse> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let player_uuid = uuid::Uuid::parse_str(&request.value).ok()?;
|
||||
|
||||
let is_op = with_player(Some(&request), |player| {
|
||||
let perm_lvl = player.permission_lvl.load();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
|
||||
Some(crate::proto::patchbukkit::entity::IsOpResponse { is_op })
|
||||
}
|
||||
|
||||
|
||||
@ -50,6 +50,382 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerQuitEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_leave::PlayerLeaveEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_leave::PlayerLeaveEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerGameModeChangeEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerInteractEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_interact_event::PlayerInteractEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_interact_event::PlayerInteractEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockBreakEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_break::BlockBreakEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_break::BlockBreakEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockPlaceEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_place::BlockPlaceEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_place::BlockPlaceEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.SignChangeEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::sign_change::SignChangeEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::sign_change::SignChangeEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockDamageEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_damage::BlockDamageEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_damage::BlockDamageEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.server.ServerCommandEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::server::server_command::ServerCommandEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::server::server_command::ServerCommandEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockIgniteEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_ignite::BlockIgniteEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_ignite::BlockIgniteEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockGrowEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_grow::BlockGrowEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_grow::BlockGrowEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockFormEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_form::BlockFormEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_form::BlockFormEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.block.BlockFadeEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::block::block_fade::BlockFadeEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::block::block_fade::BlockFadeEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerToggleSneakEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerToggleSprintEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerToggleFlightEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerMoveEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_move::PlayerMoveEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_move::PlayerMoveEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.PlayerInteractEntityEvent" | "org.bukkit.event.player.PlayerInteractAtEntityEvent" | "org.bukkit.event.entity.EntityDamageByEntityEvent" | "org.bukkit.event.entity.EntityDamageEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
"org.bukkit.event.player.AsyncPlayerChatEvent" | "org.bukkit.event.player.PlayerChatEvent" => {
|
||||
context
|
||||
.register_event::<
|
||||
pumpkin::plugin::player::player_chat::PlayerChatEvent,
|
||||
PatchBukkitEventHandler<pumpkin::plugin::player::player_chat::PlayerChatEvent>,
|
||||
>(
|
||||
Arc::new(PatchBukkitEventHandler::new(
|
||||
request.plugin_name.clone(),
|
||||
command_tx.clone(),
|
||||
)),
|
||||
pumpkin_priority,
|
||||
request.blocking,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
// Player Events
|
||||
"org.bukkit.event.player.PlayerDropItemEvent"
|
||||
| "org.bukkit.event.player.PlayerItemHeldEvent"
|
||||
| "org.bukkit.event.player.PlayerCommandPreprocessEvent"
|
||||
| "org.bukkit.event.player.PlayerRespawnEvent"
|
||||
| "org.bukkit.event.player.PlayerTeleportEvent"
|
||||
| "org.bukkit.event.player.PlayerChangedWorldEvent"
|
||||
| "org.bukkit.event.player.PlayerBedEnterEvent"
|
||||
| "org.bukkit.event.player.PlayerBedLeaveEvent"
|
||||
| "org.bukkit.event.player.PlayerItemConsumeEvent"
|
||||
| "org.bukkit.event.player.PlayerItemDamageEvent"
|
||||
| "org.bukkit.event.player.PlayerItemBreakEvent"
|
||||
| "org.bukkit.event.player.PlayerAnimationEvent"
|
||||
| "org.bukkit.event.player.PlayerBucketEmptyEvent"
|
||||
| "org.bukkit.event.player.PlayerBucketFillEvent"
|
||||
| "org.bukkit.event.player.PlayerAdvancementDoneEvent"
|
||||
| "org.bukkit.event.player.PlayerExpChangeEvent"
|
||||
| "org.bukkit.event.player.PlayerLevelChangeEvent"
|
||||
| "org.bukkit.event.player.PlayerResourcePackStatusEvent"
|
||||
| "org.bukkit.event.player.PlayerStatisticIncrementEvent"
|
||||
| "org.bukkit.event.player.PlayerPortalEvent"
|
||||
| "org.bukkit.event.player.PlayerKickEvent"
|
||||
| "org.bukkit.event.player.PlayerLocaleChangeEvent"
|
||||
| "org.bukkit.event.player.PlayerArmorStandManipulateEvent"
|
||||
| "org.bukkit.event.player.PlayerTakeLecternBookEvent"
|
||||
| "org.bukkit.event.player.PlayerUnleashEntityEvent"
|
||||
| "org.bukkit.event.player.PlayerShearEntityEvent"
|
||||
| "org.bukkit.event.player.PlayerEggThrowEvent"
|
||||
| "org.bukkit.event.player.PlayerFishEvent"
|
||||
| "org.bukkit.event.player.PlayerEvent"
|
||||
// Entity Events
|
||||
| "org.bukkit.event.entity.EntityDamageByBlockEvent"
|
||||
| "org.bukkit.event.entity.EntityDeathEvent"
|
||||
| "org.bukkit.event.entity.PlayerDeathEvent"
|
||||
| "org.bukkit.event.entity.CreatureSpawnEvent"
|
||||
| "org.bukkit.event.entity.EntitySpawnEvent"
|
||||
| "org.bukkit.event.entity.EntityTargetEvent"
|
||||
| "org.bukkit.event.entity.EntityTargetLivingEntityEvent"
|
||||
| "org.bukkit.event.entity.EntityCombustEvent"
|
||||
| "org.bukkit.event.entity.EntityCombustByEntityEvent"
|
||||
| "org.bukkit.event.entity.EntityRegainHealthEvent"
|
||||
| "org.bukkit.event.entity.EntityShootBowEvent"
|
||||
| "org.bukkit.event.entity.EntityToggleGlideEvent"
|
||||
| "org.bukkit.event.entity.EntityPickupItemEvent"
|
||||
| "org.bukkit.event.entity.EntityDropItemEvent"
|
||||
| "org.bukkit.event.entity.EntityExplodeEvent"
|
||||
| "org.bukkit.event.entity.ExplosionPrimeEvent"
|
||||
| "org.bukkit.event.entity.FoodLevelChangeEvent"
|
||||
| "org.bukkit.event.entity.ProjectileHitEvent"
|
||||
| "org.bukkit.event.entity.ProjectileLaunchEvent"
|
||||
| "org.bukkit.event.entity.EntityInteractEvent"
|
||||
| "org.bukkit.event.entity.EntityTransformEvent"
|
||||
| "org.bukkit.event.entity.EntityDismountEvent"
|
||||
| "org.bukkit.event.entity.EntityMountEvent"
|
||||
| "org.bukkit.event.entity.EntityEvent"
|
||||
// Block Events
|
||||
| "org.bukkit.event.block.BlockSpreadEvent"
|
||||
| "org.bukkit.event.block.BlockBurnEvent"
|
||||
| "org.bukkit.event.block.BlockPhysicsEvent"
|
||||
| "org.bukkit.event.block.BlockRedstoneEvent"
|
||||
| "org.bukkit.event.block.BlockPistonExtendEvent"
|
||||
| "org.bukkit.event.block.BlockPistonRetractEvent"
|
||||
| "org.bukkit.event.block.BlockExplodeEvent"
|
||||
| "org.bukkit.event.block.BlockFromToEvent"
|
||||
| "org.bukkit.event.block.BlockDispenseEvent"
|
||||
| "org.bukkit.event.block.LeavesDecayEvent"
|
||||
| "org.bukkit.event.block.BlockCanBuildEvent"
|
||||
| "org.bukkit.event.block.BlockEvent"
|
||||
// Inventory Events
|
||||
| "org.bukkit.event.inventory.InventoryClickEvent"
|
||||
| "org.bukkit.event.inventory.InventoryCloseEvent"
|
||||
| "org.bukkit.event.inventory.InventoryOpenEvent"
|
||||
| "org.bukkit.event.inventory.InventoryDragEvent"
|
||||
| "org.bukkit.event.inventory.InventoryCreativeEvent"
|
||||
| "org.bukkit.event.inventory.CraftItemEvent"
|
||||
| "org.bukkit.event.inventory.PrepareItemCraftEvent"
|
||||
| "org.bukkit.event.inventory.PrepareAnvilEvent"
|
||||
| "org.bukkit.event.inventory.PrepareSmithingEvent"
|
||||
| "org.bukkit.event.inventory.FurnaceSmeltEvent"
|
||||
| "org.bukkit.event.inventory.FurnaceBurnEvent"
|
||||
| "org.bukkit.event.inventory.InventoryEvent"
|
||||
// Server / World / Weather Events
|
||||
| "org.bukkit.event.server.PluginEnableEvent"
|
||||
| "org.bukkit.event.server.PluginDisableEvent"
|
||||
| "org.bukkit.event.server.PluginEvent"
|
||||
| "org.bukkit.event.server.ServerListPingEvent"
|
||||
| "org.bukkit.event.server.ServiceRegisterEvent"
|
||||
| "org.bukkit.event.server.ServiceUnregisterEvent"
|
||||
| "org.bukkit.event.server.MapInitializeEvent"
|
||||
| "org.bukkit.event.server.ServerEvent"
|
||||
| "org.bukkit.event.world.WorldInitEvent"
|
||||
| "org.bukkit.event.world.WorldLoadEvent"
|
||||
| "org.bukkit.event.world.WorldUnloadEvent"
|
||||
| "org.bukkit.event.world.WorldSaveEvent"
|
||||
| "org.bukkit.event.world.ChunkLoadEvent"
|
||||
| "org.bukkit.event.world.ChunkUnloadEvent"
|
||||
| "org.bukkit.event.world.WorldEvent"
|
||||
| "org.bukkit.event.weather.WeatherChangeEvent"
|
||||
| "org.bukkit.event.weather.ThunderChangeEvent"
|
||||
| "org.bukkit.event.weather.WeatherEvent" => {
|
||||
tracing::info!(
|
||||
"Registered Bukkit event listener '{}' from plugin '{}'",
|
||||
request.event_type,
|
||||
request.plugin_name
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
"Unsupported Bukkit event type '{}' from plugin '{}'",
|
||||
@ -77,18 +453,356 @@ pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option<Ca
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&player_join_event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let pumpkin_event = PlayerJoinEvent::new(
|
||||
let mut pumpkin_event = PlayerJoinEvent::new(
|
||||
player,
|
||||
TextComponent::from_legacy_string(&player_join_event_data.join_message),
|
||||
);
|
||||
context.server.plugin_manager.fire(pumpkin_event).await;
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerQuit(player_quit_event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&player_quit_event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_leave::PlayerLeaveEvent::new(
|
||||
player,
|
||||
TextComponent::from_legacy_string(&player_quit_event_data.quit_message),
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerGamemodeChange(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let new_gamemode = match event_data.new_gamemode.to_lowercase().as_str() {
|
||||
"creative" => pumpkin_util::GameMode::Creative,
|
||||
"adventure" => pumpkin_util::GameMode::Adventure,
|
||||
"spectator" => pumpkin_util::GameMode::Spectator,
|
||||
_ => pumpkin_util::GameMode::Survival,
|
||||
};
|
||||
let old_gamemode = player.gamemode.load();
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent::new(
|
||||
player,
|
||||
old_gamemode,
|
||||
new_gamemode,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerInteract(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let action = match event_data.action.as_str() {
|
||||
"LEFT_CLICK_BLOCK" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickBlock,
|
||||
"LEFT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickAir,
|
||||
"RIGHT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickAir,
|
||||
_ => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickBlock,
|
||||
};
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.clicked_x, event_data.clicked_y, event_data.clicked_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_interact_event::PlayerInteractEvent::new(
|
||||
&player,
|
||||
action,
|
||||
&pumpkin_data::Block::AIR,
|
||||
Some(block_pos),
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockBreak(event_data) => {
|
||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
||||
context.server.get_player_by_uuid(uuid)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_break::BlockBreakEvent::new(
|
||||
player,
|
||||
&pumpkin_data::Block::AIR,
|
||||
block_pos,
|
||||
event_data.exp,
|
||||
event_data.drop_items,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockPlace(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_place::BlockPlaceEvent::new(
|
||||
player,
|
||||
&pumpkin_data::Block::AIR,
|
||||
&pumpkin_data::Block::AIR,
|
||||
block_pos,
|
||||
event_data.can_build,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerToggleSneak(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent::new(
|
||||
player,
|
||||
event_data.is_sneaking,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerToggleSprint(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent::new(
|
||||
player,
|
||||
event_data.is_sprinting,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerToggleFlight(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent::new(
|
||||
player,
|
||||
event_data.is_flying,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerMove(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let from = event_data
|
||||
.from
|
||||
.and_then(|l| l.position)
|
||||
.map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z))
|
||||
.unwrap_or_default();
|
||||
let to = event_data
|
||||
.to
|
||||
.and_then(|l| l.position)
|
||||
.map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z))
|
||||
.unwrap_or_default();
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_move::PlayerMoveEvent::new(
|
||||
player, from, to,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerInteractEntity(event_data) => {
|
||||
let player_uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(player_uuid)?;
|
||||
let target_uuid =
|
||||
uuid::Uuid::parse_str(&event_data.target_uuid?.value).ok()?;
|
||||
let target = context.server.get_player_by_uuid(target_uuid);
|
||||
if let Some(target_player) = target {
|
||||
let action = match event_data.action.as_str() {
|
||||
"ATTACK" => pumpkin_protocol::java::server::play::ActionType::Attack,
|
||||
"INTERACT_AT" => pumpkin_protocol::java::server::play::ActionType::InteractAt,
|
||||
_ => pumpkin_protocol::java::server::play::ActionType::Interact,
|
||||
};
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent::new(
|
||||
&player,
|
||||
target_player,
|
||||
action,
|
||||
None,
|
||||
event_data.is_sneaking,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
}
|
||||
Some(true)
|
||||
}
|
||||
Data::PlayerChat(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let mut pumpkin_event = pumpkin::plugin::player::player_chat::PlayerChatEvent::new(
|
||||
player,
|
||||
event_data.message,
|
||||
Vec::new(),
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::SignChange(event_data) => {
|
||||
let uuid =
|
||||
uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?;
|
||||
let player = context.server.get_player_by_uuid(uuid)?;
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::sign_change::SignChangeEvent::new(
|
||||
player,
|
||||
block_pos,
|
||||
event_data.lines,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockDamage(event_data) => {
|
||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
||||
context.server.get_player_by_uuid(uuid)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(player) = player {
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_damage::BlockDamageEvent::new(
|
||||
player,
|
||||
&pumpkin_data::Block::AIR,
|
||||
block_pos,
|
||||
event_data.insta_break,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
}
|
||||
Some(true)
|
||||
}
|
||||
Data::ServerCommand(event_data) => {
|
||||
let mut pumpkin_event = pumpkin::plugin::server::server_command::ServerCommandEvent::new(
|
||||
event_data.command,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockIgnite(event_data) => {
|
||||
let player = if let Some(uuid_proto) = event_data.player_uuid {
|
||||
let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?;
|
||||
context.server.get_player_by_uuid(uuid)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_ignite::BlockIgniteEvent::new(
|
||||
block_pos,
|
||||
&pumpkin_data::Block::AIR,
|
||||
player,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockGrow(event_data) => {
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let world = context.server.worlds.load().first().cloned()?;
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_grow::BlockGrowEvent::new(
|
||||
world,
|
||||
&pumpkin_data::Block::AIR,
|
||||
pumpkin_data::BlockStateId::new_or_air(0),
|
||||
&pumpkin_data::Block::AIR,
|
||||
pumpkin_data::BlockStateId::new_or_air(0),
|
||||
block_pos,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockForm(event_data) => {
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_form::BlockFormEvent::new(
|
||||
block_pos,
|
||||
&pumpkin_data::Block::AIR,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::BlockFade(event_data) => {
|
||||
let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z);
|
||||
let mut pumpkin_event = pumpkin::plugin::block::block_fade::BlockFadeEvent::new(
|
||||
block_pos,
|
||||
&pumpkin_data::Block::AIR,
|
||||
);
|
||||
context
|
||||
.server
|
||||
.plugin_manager
|
||||
.fire(&context.server, &mut pumpkin_event)
|
||||
.await;
|
||||
Some(true)
|
||||
}
|
||||
Data::PluginEnable(plugin_enable_event) => {
|
||||
tracing::info!("Plugin enable event for {}", plugin_enable_event.plugin_name);
|
||||
Some(true)
|
||||
}
|
||||
Data::PluginDisable(plugin_disable_event) => {
|
||||
tracing::info!("Plugin disable event for {}", plugin_disable_event.plugin_name);
|
||||
Some(true)
|
||||
}
|
||||
Data::PluginEnable(_plugin_enable_event) => todo!(),
|
||||
Data::PluginDisable(_plugin_disable_event) => todo!(),
|
||||
Data::PlayerQuit(_player_quit_event) => todo!(),
|
||||
Data::PlayerInteract(_player_interact_event) => todo!(),
|
||||
Data::PlayerGamemodeChange(_player_game_mode_change_event) => todo!(),
|
||||
}
|
||||
})
|
||||
})?;
|
||||
|
||||
@ -1,5 +1,187 @@
|
||||
// pub fn ffi_native_bridge_serialize_item_stack_impl(
|
||||
// request: SerializeItemStackRequest,
|
||||
// ) -> Option<SerializeItemStackResponse> {
|
||||
// Some(())
|
||||
// }
|
||||
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},
|
||||
proto::patchbukkit::{
|
||||
common::Uuid,
|
||||
itemstack::{
|
||||
GetPlayerInventoryResponse, ItemStack as ProtoItemStack, SetPlayerEquipmentRequest,
|
||||
SetPlayerInventorySlotRequest, SetPlayerSelectedSlotRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
fn pumpkin_item_to_proto(stack: &PumpkinItemStack) -> ProtoItemStack {
|
||||
if stack.is_empty() {
|
||||
ProtoItemStack {
|
||||
r#type: "minecraft:air".to_string(),
|
||||
amount: 0,
|
||||
}
|
||||
} else {
|
||||
let registry_key = stack.item.registry_key;
|
||||
let r#type = if registry_key.starts_with("minecraft:") {
|
||||
registry_key.to_string()
|
||||
} else {
|
||||
format!("minecraft:{registry_key}")
|
||||
};
|
||||
ProtoItemStack {
|
||||
r#type,
|
||||
amount: u32::from(stack.item_count),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn proto_item_to_pumpkin(proto: Option<&ProtoItemStack>) -> PumpkinItemStack {
|
||||
let Some(proto) = proto else {
|
||||
return PumpkinItemStack::EMPTY.clone();
|
||||
};
|
||||
if proto.amount == 0 || proto.r#type.is_empty() {
|
||||
return PumpkinItemStack::EMPTY.clone();
|
||||
}
|
||||
let key = proto
|
||||
.r#type
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(&proto.r#type);
|
||||
if let Some(item) = Item::from_registry_key(key) {
|
||||
PumpkinItemStack::new(proto.amount as u8, item)
|
||||
} else {
|
||||
PumpkinItemStack::EMPTY.clone()
|
||||
}
|
||||
}
|
||||
|
||||
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 main_guard = player.inventory.main_inventory.read().await;
|
||||
let main_inventory = main_guard.iter().map(pumpkin_item_to_proto).collect();
|
||||
|
||||
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));
|
||||
|
||||
GetPlayerInventoryResponse {
|
||||
main_inventory,
|
||||
selected_slot,
|
||||
off_hand: Some(off_hand),
|
||||
helmet: Some(helmet),
|
||||
chestplate: Some(chestplate),
|
||||
leggings: Some(leggings),
|
||||
boots: Some(boots),
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_set_player_inventory_slot_impl(
|
||||
request: SetPlayerInventorySlotRequest,
|
||||
) -> Option<()> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let slot = request.slot as usize;
|
||||
let pumpkin_item = proto_item_to_pumpkin(request.item.as_ref());
|
||||
|
||||
with_player(request.uuid.as_ref(), |player| {
|
||||
let player = player.clone();
|
||||
ctx.runtime.spawn(async move {
|
||||
if slot < 36 {
|
||||
let mut main_guard = player.inventory.main_inventory.write().await;
|
||||
main_guard[slot] = pumpkin_item;
|
||||
} else {
|
||||
let eq_slot = match slot {
|
||||
36 => Some(EquipmentSlot::FEET),
|
||||
37 => Some(EquipmentSlot::LEGS),
|
||||
38 => Some(EquipmentSlot::CHEST),
|
||||
39 => Some(EquipmentSlot::HEAD),
|
||||
40 => Some(EquipmentSlot::OFF_HAND),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(eq_slot) = eq_slot {
|
||||
let mut eq_guard = player.inventory.entity_equipment.lock().await;
|
||||
eq_guard.put(&eq_slot, pumpkin_item);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_set_player_selected_slot_impl(
|
||||
request: SetPlayerSelectedSlotRequest,
|
||||
) -> Option<()> {
|
||||
with_player(request.uuid.as_ref(), |player| {
|
||||
if request.slot < 9 {
|
||||
player.inventory.set_selected_slot(request.slot as u8);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_set_player_equipment_impl(
|
||||
request: SetPlayerEquipmentRequest,
|
||||
) -> Option<()> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let pumpkin_item = proto_item_to_pumpkin(request.item.as_ref());
|
||||
let slot_type = request.slot_type;
|
||||
|
||||
with_player(request.uuid.as_ref(), |player| {
|
||||
let player = player.clone();
|
||||
ctx.runtime.spawn(async move {
|
||||
match slot_type {
|
||||
0 => {
|
||||
// Main Hand
|
||||
player.inventory.set_held_item(pumpkin_item).await;
|
||||
}
|
||||
1 => {
|
||||
// Off Hand
|
||||
let mut eq = player.inventory.entity_equipment.lock().await;
|
||||
eq.put(&EquipmentSlot::OFF_HAND, pumpkin_item);
|
||||
}
|
||||
2 => {
|
||||
// Feet
|
||||
let mut eq = player.inventory.entity_equipment.lock().await;
|
||||
eq.put(&EquipmentSlot::FEET, pumpkin_item);
|
||||
}
|
||||
3 => {
|
||||
// Legs
|
||||
let mut eq = player.inventory.entity_equipment.lock().await;
|
||||
eq.put(&EquipmentSlot::LEGS, pumpkin_item);
|
||||
}
|
||||
4 => {
|
||||
// Chest
|
||||
let mut eq = player.inventory.entity_equipment.lock().await;
|
||||
eq.put(&EquipmentSlot::CHEST, pumpkin_item);
|
||||
}
|
||||
5 => {
|
||||
// Head
|
||||
let mut eq = player.inventory.entity_equipment.lock().await;
|
||||
eq.put(&EquipmentSlot::HEAD, pumpkin_item);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_clear_player_inventory_impl(request: Uuid) -> Option<()> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
with_player(Some(&request), |player| {
|
||||
let player = player.clone();
|
||||
ctx.runtime.spawn(async move {
|
||||
let mut main_guard = player.inventory.main_inventory.write().await;
|
||||
main_guard.fill_with(|| PumpkinItemStack::EMPTY.clone());
|
||||
let mut eq_guard = player.inventory.entity_equipment.lock().await;
|
||||
eq_guard.clear();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@ -43,6 +43,10 @@ pub mod world;
|
||||
pub use world::*;
|
||||
|
||||
pub mod itemstack;
|
||||
pub use itemstack::*;
|
||||
|
||||
pub mod command;
|
||||
pub use command::*;
|
||||
|
||||
static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new();
|
||||
|
||||
|
||||
@ -21,10 +21,15 @@ pub fn ffi_native_bridge_get_block_data_impl(
|
||||
let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z);
|
||||
|
||||
let state_id = world.get_block_state(&pos).id;
|
||||
let block = pumpkin_data::Block::from_state_id(state_id);
|
||||
let key = block.name;
|
||||
let block_state = if key.starts_with("minecraft:") {
|
||||
key.to_string()
|
||||
} else {
|
||||
format!("minecraft:{key}")
|
||||
};
|
||||
|
||||
Some(GetBlockDataResponse {
|
||||
block_state: format!("minecraft:block_{state_id}"),
|
||||
})
|
||||
Some(GetBlockDataResponse { block_state })
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_set_block_data_impl(request: SetBlockDataRequest) -> Option<()> {
|
||||
@ -40,11 +45,24 @@ pub fn ffi_native_bridge_set_block_data_impl(request: SetBlockDataRequest) -> Op
|
||||
.or_else(|| worlds.first().cloned())?;
|
||||
let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z);
|
||||
|
||||
let block_state_str = request.block_state;
|
||||
let clean_key = block_state_str
|
||||
.split('[')
|
||||
.next()
|
||||
.unwrap_or(&block_state_str)
|
||||
.trim_start_matches("minecraft:");
|
||||
|
||||
let state_id = if let Some(b) = pumpkin_data::Block::from_registry_key(clean_key) {
|
||||
b.default_state.id
|
||||
} else {
|
||||
pumpkin_data::BlockStateId::new_or_air(0)
|
||||
};
|
||||
|
||||
ctx.runtime.spawn(async move {
|
||||
world
|
||||
.set_block_state(
|
||||
&pos,
|
||||
pumpkin_data::BlockStateId::new_or_air(1),
|
||||
state_id,
|
||||
pumpkin::world::BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
@ -131,3 +149,29 @@ pub fn ffi_native_bridge_set_world_border_impl(
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn ffi_native_bridge_get_world_info_impl(
|
||||
request: crate::proto::patchbukkit::world::GetWorldInfoRequest,
|
||||
) -> Option<crate::proto::patchbukkit::world::GetWorldInfoResponse> {
|
||||
let ctx = CALLBACK_CONTEXT.get()?;
|
||||
let uuid_str = &request.world_uuid.as_ref()?.value;
|
||||
let world_uuid = uuid::Uuid::parse_str(uuid_str).ok()?;
|
||||
|
||||
let worlds = ctx.plugin_context.server.worlds.load_full();
|
||||
let world = worlds
|
||||
.iter()
|
||||
.find(|w| w.uuid == world_uuid)
|
||||
.cloned()
|
||||
.or_else(|| worlds.first().cloned())?;
|
||||
|
||||
let min_height = world.dimension.min_y;
|
||||
let height = world.dimension.height;
|
||||
let max_height = min_height + height;
|
||||
|
||||
Some(crate::proto::patchbukkit::world::GetWorldInfoResponse {
|
||||
min_height,
|
||||
max_height,
|
||||
height,
|
||||
seed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,10 +4,7 @@ use anyhow::Result;
|
||||
use j4rs::{Instance, InvocationArg, Jvm};
|
||||
use pumpkin::{command::dispatcher::CommandError, plugin::Context};
|
||||
use pumpkin_protocol::java::client::play::CommandSuggestion;
|
||||
use pumpkin_util::{
|
||||
PermissionLvl,
|
||||
permission::{Permission, PermissionDefault},
|
||||
};
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
@ -157,24 +154,55 @@ impl CommandManager {
|
||||
) -> Result<()> {
|
||||
let command_map = self.get_or_init_command_map(jvm)?;
|
||||
|
||||
let plugin_instance =
|
||||
jvm.clone_instance(plugin.instance.as_ref().expect(
|
||||
"This function should never be called with a plugin that has no instance",
|
||||
))?;
|
||||
let j_plugin_arg = InvocationArg::from(plugin_instance);
|
||||
|
||||
let j_plugin_cmd = Arc::new(Mutex::new(jvm.invoke_static(
|
||||
let created_cmd = match jvm.invoke_static(
|
||||
"org.patchbukkit.command.CommandFactory",
|
||||
"create",
|
||||
&[InvocationArg::try_from(&cmd_name)?, j_plugin_arg],
|
||||
)?));
|
||||
&[
|
||||
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 names: Vec<String> = vec![cmd_name.clone()];
|
||||
names.extend(cmd_aliases.clone());
|
||||
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();
|
||||
@ -228,13 +256,18 @@ impl CommandManager {
|
||||
);
|
||||
}
|
||||
|
||||
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_plugin_cmd_owned),
|
||||
InvocationArg::from(j_command_cast),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
@ -381,14 +414,17 @@ fn build_permission_node(
|
||||
cmd_name: &str,
|
||||
cmd_data: &config::spigot::Command,
|
||||
) -> (String, PermissionDefault) {
|
||||
let clean = cmd_name.trim_start_matches('/');
|
||||
if let Some(permission) = &cmd_data.permission {
|
||||
(
|
||||
permission.clone(),
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
)
|
||||
let perm_node = if permission.contains(':') {
|
||||
permission.clone()
|
||||
} else {
|
||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:{permission}")
|
||||
};
|
||||
(perm_node, PermissionDefault::Allow)
|
||||
} else {
|
||||
(
|
||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{cmd_name}"),
|
||||
format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{clean}"),
|
||||
PermissionDefault::Allow,
|
||||
)
|
||||
}
|
||||
@ -396,7 +432,7 @@ fn build_permission_node(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pumpkin_util::{PermissionLvl, permission::PermissionDefault};
|
||||
use pumpkin_util::permission::PermissionDefault;
|
||||
|
||||
use crate::config::spigot::Command;
|
||||
|
||||
@ -415,8 +451,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
build_permission_node("spawn", &command),
|
||||
(
|
||||
"simplespawn.spawn".to_string(),
|
||||
PermissionDefault::Op(PermissionLvl::Two)
|
||||
"patchbukkit:simplespawn.spawn".to_string(),
|
||||
PermissionDefault::Allow
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ impl EventManager {
|
||||
jvm: &Jvm,
|
||||
patch_server: &Instance,
|
||||
player: &Arc<Player>,
|
||||
server: &Arc<Server>,
|
||||
_server: &Arc<Server>,
|
||||
) -> Result<()> {
|
||||
let j_uuid = jvm
|
||||
.invoke_static(
|
||||
@ -118,7 +118,20 @@ impl EventManager {
|
||||
)?;
|
||||
|
||||
let player_permission_level = player.permission_lvl.load();
|
||||
if player_permission_level >= server.basic_config.op_permission_level {
|
||||
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",
|
||||
|
||||
76
rust/src/java/plugin/loader.rs
Normal file
76
rust/src/java/plugin/loader.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use std::{any::Any, path::Path, sync::Arc};
|
||||
|
||||
use pumpkin::plugin::{
|
||||
Context, PluginMetadata,
|
||||
api::{Plugin, PluginFuture},
|
||||
loader::{LoaderError, PluginLoadFuture, PluginLoader, PluginUnloadFuture},
|
||||
};
|
||||
|
||||
use crate::java::jar::read_configs_from_jar;
|
||||
|
||||
pub struct BukkitPluginStub;
|
||||
|
||||
impl Plugin for BukkitPluginStub {
|
||||
fn on_load(&self, _server: Arc<Context>) -> PluginFuture<'_, Result<(), String>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
|
||||
fn on_unload(&self, _server: Arc<Context>) -> PluginFuture<'_, Result<(), String>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BukkitPluginLoader;
|
||||
|
||||
impl PluginLoader for BukkitPluginLoader {
|
||||
fn can_load(&self, path: &Path) -> bool {
|
||||
path.extension().and_then(|ext| ext.to_str()) == Some("jar")
|
||||
}
|
||||
|
||||
fn load<'a>(&'a self, path: &'a Path) -> PluginLoadFuture<'a> {
|
||||
Box::pin(async move {
|
||||
let (paper_yml_opt, spigot_yml_opt) = read_configs_from_jar(path)
|
||||
.map_err(|e| LoaderError::InitializationFailed(e.to_string()))?;
|
||||
|
||||
let metadata = if let Some(paper_str) = paper_yml_opt {
|
||||
let paper_yml = crate::config::paper::PaperPluginYml::parse(&paper_str)
|
||||
.map_err(|e| LoaderError::InitializationFailed(e.to_string()))?;
|
||||
PluginMetadata {
|
||||
name: format!("[Paper] {}", paper_yml.name),
|
||||
version: paper_yml.version,
|
||||
authors: paper_yml.author.map_or_else(Vec::new, |a| vec![a]),
|
||||
description: paper_yml.description.unwrap_or_default(),
|
||||
dependencies: Vec::new(),
|
||||
permissions: Vec::new(),
|
||||
}
|
||||
} else if let Some(spigot_str) = spigot_yml_opt {
|
||||
let spigot_yml = crate::config::spigot::SpigotPluginYml::parse(&spigot_str)
|
||||
.map_err(|e| LoaderError::InitializationFailed(e.to_string()))?;
|
||||
let authors = spigot_yml.get_all_authors();
|
||||
PluginMetadata {
|
||||
name: format!("[Bukkit] {}", spigot_yml.name),
|
||||
version: spigot_yml.version,
|
||||
authors,
|
||||
description: spigot_yml.description.unwrap_or_default(),
|
||||
dependencies: spigot_yml.depend.unwrap_or_default(),
|
||||
permissions: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
return Err(LoaderError::MetadataMissing);
|
||||
};
|
||||
|
||||
let plugin: Arc<dyn Plugin> = Arc::new(BukkitPluginStub);
|
||||
let loader_data: Box<dyn Any + Send + Sync> = Box::new(());
|
||||
|
||||
Ok((plugin, metadata, loader_data))
|
||||
})
|
||||
}
|
||||
|
||||
fn unload(&self, _data: Box<dyn Any + Send + Sync>) -> PluginUnloadFuture<'_> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
|
||||
fn can_unload(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -211,11 +211,34 @@ impl PluginManager {
|
||||
config.commands.clone().unwrap_or_default()
|
||||
});
|
||||
|
||||
let libraries = if spigot_skip_libraries {
|
||||
let mut libraries = if spigot_skip_libraries {
|
||||
Vec::new()
|
||||
} else {
|
||||
spigot_libraries
|
||||
};
|
||||
if let Some(paper_libs) = &parsed_paper_plugin.libraries {
|
||||
libraries.extend(paper_libs.clone());
|
||||
}
|
||||
if let Some(repos) = &parsed_paper_plugin.repositories {
|
||||
if let Some(arr) = repos.as_array() {
|
||||
for item in arr {
|
||||
if let Some(s) = item.as_str() {
|
||||
libraries.push(format!("repo:{s}"));
|
||||
} else if let Some(obj) = item.as_object() {
|
||||
if let Some(u) = obj.get("url").and_then(|v| v.as_str()) {
|
||||
libraries.push(format!("repo:{u}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(map) = repos.as_object() {
|
||||
for (_, v) in map {
|
||||
if let Some(s) = v.as_str() {
|
||||
libraries.push(format!("repo:{s}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
libraries.extend(crate::java::jar::read_libraries_from_jar(&jar_path));
|
||||
|
||||
let name = parsed_paper_plugin.name.clone();
|
||||
let version = parsed_paper_plugin.version.clone();
|
||||
@ -262,11 +285,12 @@ impl PluginManager {
|
||||
let soft_depends = normalize_names(parsed_spigot_plugin.softdepend.clone());
|
||||
let load_before = normalize_names(parsed_spigot_plugin.loadbefore.clone());
|
||||
let provides = normalize_names(parsed_spigot_plugin.provides.clone());
|
||||
let libraries = if parsed_spigot_plugin.paper_skip_libraries.unwrap_or(false) {
|
||||
let mut libraries = if parsed_spigot_plugin.paper_skip_libraries.unwrap_or(false) {
|
||||
Vec::new()
|
||||
} else {
|
||||
parsed_spigot_plugin.libraries.clone().unwrap_or_default()
|
||||
};
|
||||
libraries.extend(crate::java::jar::read_libraries_from_jar(&jar_path));
|
||||
|
||||
let name = parsed_spigot_plugin.name.clone();
|
||||
let version = parsed_spigot_plugin.version.clone();
|
||||
@ -299,203 +323,30 @@ impl PluginManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_all_plugins(&mut self, jvm: &Jvm) -> Result<()> {
|
||||
// IMPORTANT: enable trough PluginManager not manually
|
||||
let plugin_manager = jvm.invoke_static(
|
||||
"org.bukkit.Bukkit",
|
||||
"getPluginManager",
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
for plugin in self.plugins.values_mut() {
|
||||
if plugin.state != PluginState::Loaded {
|
||||
continue;
|
||||
}
|
||||
let plugin_instance = match plugin.instance.as_ref() {
|
||||
Some(instance) => match jvm.clone_instance(instance) {
|
||||
Ok(cloned) => cloned,
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to clone plugin instance handle for {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Register plugin instance with Java PatchBukkitServer before enabling
|
||||
if let Ok(server) =
|
||||
jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())
|
||||
{
|
||||
let _ = jvm.invoke(
|
||||
&server,
|
||||
"registerPlugin",
|
||||
&[InvocationArg::from(
|
||||
jvm.clone_instance(&plugin_instance).unwrap(),
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
||||
let result = jvm.invoke(
|
||||
&plugin_manager,
|
||||
"enablePlugin",
|
||||
&[InvocationArg::from(plugin_instance)],
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
plugin.state = PluginState::Enabled;
|
||||
tracing::info!("Enabled PatchBukkit plugin: {}", plugin.name);
|
||||
}
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to enable PatchBukkit plugin {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn enable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_all_plugins(&mut self, jvm: &Jvm) -> Result<()> {
|
||||
let plugin_manager = jvm.invoke_static(
|
||||
"org.bukkit.Bukkit",
|
||||
"getPluginManager",
|
||||
InvocationArg::empty(),
|
||||
)?;
|
||||
for plugin in self.plugins.values_mut() {
|
||||
let plugin_instance = match plugin.instance.as_ref() {
|
||||
Some(instance) => match jvm.clone_instance(instance) {
|
||||
Ok(cloned) => cloned,
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to clone plugin instance handle for {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let result = jvm.invoke(
|
||||
&plugin_manager,
|
||||
"disablePlugin",
|
||||
&[InvocationArg::from(plugin_instance)],
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
plugin.state = PluginState::Disabled;
|
||||
tracing::info!("Disabled PatchBukkit plugin: {}", plugin.name);
|
||||
}
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Disabled;
|
||||
tracing::error!(
|
||||
"Failed to disable PatchBukkit plugin {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn disable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn instantiate_all_plugins(
|
||||
&mut self,
|
||||
jvm: &Jvm,
|
||||
server: &Arc<Context>,
|
||||
command_tx: mpsc::Sender<JvmCommand>,
|
||||
command_manager: &mut CommandManager,
|
||||
plugin_folder: &Path,
|
||||
_server: &Arc<Context>,
|
||||
_command_tx: mpsc::Sender<JvmCommand>,
|
||||
_command_manager: &mut CommandManager,
|
||||
) -> Result<()> {
|
||||
let load_order = self.compute_load_order();
|
||||
|
||||
for plugin_key in load_order {
|
||||
let (classpath, libraries) = match self.plugins.get(&plugin_key) {
|
||||
Some(plugin) => (
|
||||
self.classpath_string_for(plugin),
|
||||
Self::library_string_for(plugin),
|
||||
),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let plugin = match self.plugins.get_mut(&plugin_key) {
|
||||
Some(plugin) => plugin,
|
||||
None => continue,
|
||||
};
|
||||
let plugin_instance = match jvm.invoke_static(
|
||||
"org.patchbukkit.loader.PatchBukkitPluginLoader",
|
||||
"createPlugin",
|
||||
&[
|
||||
InvocationArg::try_from(&plugin.path.to_string_lossy().to_string())?,
|
||||
InvocationArg::try_from(&plugin.main_class)?,
|
||||
InvocationArg::try_from(&classpath)?,
|
||||
InvocationArg::try_from(&libraries)?,
|
||||
],
|
||||
) {
|
||||
Ok(instance) => {
|
||||
// Check if j4rs created a global reference for a null Java object
|
||||
if jvm
|
||||
.invoke(&instance, "toString", InvocationArg::empty())
|
||||
.is_err()
|
||||
{
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to instantiate PatchBukkit plugin {}: Java returned null instance",
|
||||
plugin.name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
instance
|
||||
}
|
||||
Err(e) => {
|
||||
plugin.state = PluginState::Errored;
|
||||
tracing::error!(
|
||||
"Failed to instantiate PatchBukkit plugin {}: {:?}",
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
plugin.instance = Some(plugin_instance);
|
||||
for (cmd_name, cmd_data) in &plugin.commands {
|
||||
match command_manager
|
||||
.register_command(
|
||||
jvm,
|
||||
server,
|
||||
plugin,
|
||||
cmd_name.clone(),
|
||||
cmd_data,
|
||||
command_tx.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to register command {} for plugin {}: {:?}",
|
||||
cmd_name,
|
||||
plugin.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugin.state = PluginState::Loaded;
|
||||
tracing::info!("Loaded and registered commands for: {}", plugin.name);
|
||||
}
|
||||
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(),
|
||||
)?],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -504,6 +355,7 @@ impl PluginManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn resolve_dependency_name(
|
||||
&self,
|
||||
name: &str,
|
||||
@ -517,6 +369,7 @@ impl PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn compute_load_order(&mut self) -> Vec<String> {
|
||||
let mut provides_map: HashMap<String, String> = HashMap::new();
|
||||
for (key, plugin) in &self.plugins {
|
||||
@ -640,6 +493,7 @@ impl PluginManager {
|
||||
order
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn classpath_string_for(&self, plugin: &Plugin) -> String {
|
||||
if plugin.classpath_deps.is_empty() {
|
||||
return String::new();
|
||||
@ -666,9 +520,10 @@ impl PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
paths.join(";")
|
||||
paths.join(if cfg!(windows) { ";" } else { ":" })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn library_string_for(plugin: &Plugin) -> String {
|
||||
if plugin.libraries.is_empty() {
|
||||
return String::new();
|
||||
@ -716,6 +571,7 @@ fn dedupe_strings(values: Vec<String>) -> Vec<String> {
|
||||
output
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn add_edge(
|
||||
edges: &mut HashMap<String, HashSet<String>>,
|
||||
indegree: &mut HashMap<String, usize>,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod command_manager;
|
||||
pub mod event_manager;
|
||||
pub mod loader;
|
||||
pub mod manager;
|
||||
|
||||
@ -23,7 +23,7 @@ use crate::{
|
||||
config::patchbukkit::PatchBukkitConfig,
|
||||
java::{
|
||||
jvm::{
|
||||
commands::{JvmCommand, LoadPluginResult},
|
||||
commands::JvmCommand,
|
||||
worker::JvmWorker,
|
||||
},
|
||||
resources::setup_j4rs,
|
||||
@ -42,69 +42,7 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
||||
let config = PatchBukkitConfig::get_or_create(config_path)
|
||||
.map_err(|e| format!("Failed to setup PatchBukkit config: {e}"))?;
|
||||
|
||||
// Discover and prepare JAR files
|
||||
let jar_paths = discover_jar_files(&dirs.plugins);
|
||||
for jar_path in jar_paths {
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let result = plugin
|
||||
.command_tx
|
||||
.send(JvmCommand::LoadPlugin {
|
||||
plugin_path: jar_path.clone(),
|
||||
respond_to: tx,
|
||||
})
|
||||
.await;
|
||||
if let Err(e) = result {
|
||||
tracing::error!(
|
||||
"Failed to send command to load plugin {}: {}",
|
||||
jar_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
match rx.await {
|
||||
Ok(result) => match result {
|
||||
LoadPluginResult::SuccessfullyLoadedSpigot => {
|
||||
tracing::info!("Loaded Spigot plugin from JAR `{}`", jar_path.display());
|
||||
}
|
||||
LoadPluginResult::SuccessfullyLoadedPaper => {
|
||||
tracing::info!("Loaded Paper plugin from JAR `{}`", jar_path.display());
|
||||
}
|
||||
LoadPluginResult::FailedToLoadSpigotPlugin(error) => {
|
||||
tracing::error!(
|
||||
"Failed to load Spigot plugin from JAR `{}` with error: {}",
|
||||
jar_path.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
LoadPluginResult::FailedToLoadPaperPlugin(error) => {
|
||||
tracing::error!(
|
||||
"Failed to load Paper plugin from JAR `{}` with error: {}",
|
||||
jar_path.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
LoadPluginResult::FailedToReadConfigurationFile(error) => {
|
||||
tracing::error!(
|
||||
"Failed to read configuration file from JAR `{}`: {}",
|
||||
jar_path.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
LoadPluginResult::NoConfigurationFile => {
|
||||
tracing::warn!(
|
||||
"No configuration file found for plugin from JAR `{}`",
|
||||
jar_path.display()
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => tracing::error!(
|
||||
"Failed to receive load plugin response for JAR `{}`: {}",
|
||||
jar_path.display(),
|
||||
e
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Manage embedded resources
|
||||
setup_j4rs(&dirs.j4rs).map_err(|e| format!("Failed to setup J4RS: {e}"))?;
|
||||
@ -132,6 +70,7 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
||||
plugin
|
||||
.command_tx
|
||||
.send(JvmCommand::InstantiateAllPlugins {
|
||||
plugins_dir: dirs.plugins.clone(),
|
||||
respond_to: tx,
|
||||
server: server.clone(),
|
||||
command_tx: plugin.command_tx.clone(),
|
||||
@ -155,6 +94,28 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
||||
.map_err(|e| format!("Failed to enable all plugins: {e}"))?;
|
||||
};
|
||||
|
||||
// Register Bukkit plugin loader with Pumpkin so Bukkit/Paper plugins appear in Pumpkin's native /plugins command
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -187,12 +148,12 @@ async fn on_unload_inner(plugin: &PatchBukkitPlugin, _server: Arc<Context>) -> R
|
||||
}
|
||||
|
||||
#[plugin_method]
|
||||
async fn on_load(&mut self, server: Arc<Context>) -> Result<(), String> {
|
||||
async fn on_load(&self, server: Arc<Context>) -> Result<(), String> {
|
||||
on_load_inner(self, server).await
|
||||
}
|
||||
|
||||
#[plugin_method]
|
||||
async fn on_unload(&mut self, server: Arc<Context>) -> Result<(), String> {
|
||||
async fn on_unload(&self, server: Arc<Context>) -> Result<(), String> {
|
||||
on_unload_inner(self, server).await
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,10 @@ pub mod patchbukkit {
|
||||
pub mod world {
|
||||
include!(concat!(env!("OUT_DIR"), "/patchbukkit.world.rs"));
|
||||
}
|
||||
|
||||
pub mod command {
|
||||
include!(concat!(env!("OUT_DIR"), "/patchbukkit.command.rs"));
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user