Skip to content

Commit

Permalink
fix: make BukkitResourcePackRequestSender use API methods
Browse files Browse the repository at this point in the history
Makes the class use reflection only to check which methods are present. Adds compatibility for 1.20.2 (It seems like they removed the setResourcePack method from Player, and now you have to manually send the resource pack packet). Tries to prefer Paper API over Spigot API. Removes support for Â'required 'and p'rompt 'options for all Spigot versions.
  • Loading branch information
yusshu committed Sep 28, 2023
1 parent e9b32f3 commit 65b1837
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 81 deletions.
2 changes: 1 addition & 1 deletion bukkit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies {
implementation(libs.creative.serializer.minecraft)
implementation(project(":creative-central-api"))
implementation(project(":creative-central-common"))
compileOnly("io.papermc.paper:paper-api:1.19.3-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT")
}

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,106 +24,83 @@
package team.unnamed.creative.central.bukkit.request;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bukkit.entity.Player;
import team.unnamed.creative.central.bukkit.util.Version;
import team.unnamed.creative.central.request.ResourcePackRequest;
import team.unnamed.creative.central.request.ResourcePackRequestSender;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;

public final class BukkitResourcePackRequestSender implements ResourcePackRequestSender {

private static final ResourcePackRequestSender INSTANCE = new BukkitResourcePackRequestSender();
private final BiConsumer<Player, ResourcePackRequest> delegate;

private static final Method SET_RESOURCE_PACK_METHOD;
private static final Method GET_HANDLE_METHOD;
private static final @Nullable Method DESERIALIZE_COMPONENT_METHOD;

static {
try {
Class<?> craftPlayerClass = Class.forName("org.bukkit.craftbukkit."
+ Version.CURRENT + ".entity.CraftPlayer");

GET_HANDLE_METHOD = craftPlayerClass.getDeclaredMethod("getHandle");

if (Version.CURRENT.minor() < 17) {

// we use getHandle() and then .setResourcePack(String, String)
// compatible with both Spigot and Paper
SET_RESOURCE_PACK_METHOD = Class.forName("net.minecraft.server." + Version.CURRENT + ".EntityPlayer")
.getDeclaredMethod("setResourcePack", String.class, String.class);

DESERIALIZE_COMPONENT_METHOD = null;
} else {

// we use getHandle() and then setResourcePack(String, String, boolean, IChatBaseComponent)
// for 1.17 or "a" method for and 1.18, compatible with both Spigot and Paper
SET_RESOURCE_PACK_METHOD = Class.forName("net.minecraft.server.level.EntityPlayer")
.getDeclaredMethod(
Version.CURRENT.minor() == 17 ? "setResourcePack" : "a",
String.class,
String.class,
boolean.class,
Class.forName("net.minecraft.network.chat.IChatBaseComponent")
);

DESERIALIZE_COMPONENT_METHOD = Class.forName(
"net.minecraft.network.chat.IChatBaseComponent$ChatSerializer"
).getDeclaredMethod("a", String.class);
}
} catch (ReflectiveOperationException e) {
// probably found an unsupported version of spigot
throw new IllegalStateException(
"Cannot find setResourcePack method",
e
);
}
}

private BukkitResourcePackRequestSender() {
private BukkitResourcePackRequestSender(BiConsumer<Player, ResourcePackRequest> delegate) {
this.delegate = delegate;
}

@Override
@SuppressWarnings("JavaReflectionInvocation") // ide detects parameter mismatch
public void send(Object playerObject, ResourcePackRequest request) {
if (!(playerObject instanceof Player)) {
throw new IllegalArgumentException("Provided 'player' is not an actual Bukkit Player: " + playerObject);
}

Player player = (Player) playerObject;
try {
Object handle = GET_HANDLE_METHOD.invoke(player);
delegate.accept((Player) playerObject, request);
}

@SuppressWarnings("deprecation")
public static ResourcePackRequestSender bukkit() {

if (Version.CURRENT.minor() < 17) {
// 'required' and 'prompt' fields not supported
SET_RESOURCE_PACK_METHOD.invoke(
handle,
request.url(),
request.hash()
);
} else {
Component prompt = request.prompt();
String jsonPrompt = prompt == null ? null : GsonComponentSerializer.gson().serialize(prompt);
SET_RESOURCE_PACK_METHOD.invoke(
handle,
request.url(),
request.hash(),
request.required(),
jsonPrompt == null ? null : DESERIALIZE_COMPONENT_METHOD.invoke(null, jsonPrompt)
);
}
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(
"Cannot apply resource pack",
e
);
if (isSetResourcePackOverrideAvailable(String.class, String.class, boolean.class, Component.class)) {
// Method that accepts: URL, Hash, Required and Prompt options,
// available since Paper 1.17 (We are currently on 1.20)
return new BukkitResourcePackRequestSender((player, request) -> player.setResourcePack(
request.url(),
request.hash(),
request.required(),
request.prompt()
));
}

if (isSetResourcePackOverrideAvailable(String.class, String.class)) {
// Method that accepts URL and Hash options
// available since Paper 1.12 and probably before too
return new BukkitResourcePackRequestSender((player, request) -> player.setResourcePack(
request.url(),
request.hash()
));
}

if (isSetResourcePackOverrideAvailable(String.class, byte[].class)) {
// Stupid method that accepts URL and Hash options,
// but only accepts hash in a byte array. It's stupid
// because it will internally convert the byte array to
// a string again, to send it using the resource pack
// packet
return new BukkitResourcePackRequestSender((player, request) -> {
// convert to hash byte aray
String hs = request.hash();
int len = hs.length();
byte[] hash = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
hash[i / 2] = (byte) ((Character.digit(hs.charAt(i), 16) << 4)
+ Character.digit(hs.charAt(i + 1), 16));
}

// now call the stupid method
player.setResourcePack(request.url(), hash);
});
}

throw new IllegalStateException("No supported setResourcePack method found on Player!");
}

public static ResourcePackRequestSender bukkit() {
return INSTANCE;
private static boolean isSetResourcePackOverrideAvailable(Class<?>... argumentTypes) {
try {
Player.class.getDeclaredMethod("setResourcePack", argumentTypes);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}

}

0 comments on commit 65b1837

Please sign in to comment.