Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bukkit 1.20.5 support #163

Merged
merged 3 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ final class BukkitAudience extends FacetAudience<CommandSender> {
);
private static final Collection<Facet.Book<Player, ?, ?>> BOOK = Facet.of(
// () -> new SpigotFacet.Book(),
() -> new CraftBukkitFacet.Book_1_20_5(),
() -> new CraftBukkitFacet.BookPost1_13(),
() -> new CraftBukkitFacet.Book1_13(),
() -> new CraftBukkitFacet.BookPre1_13());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;

import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findConstructor;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findCraftClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findEnum;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findField;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findMcClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findMcClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findNmsClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findStaticMethod;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.lookup;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.searchMethod;
import static net.kyori.adventure.platform.facet.Knob.logError;
Expand Down Expand Up @@ -92,14 +98,17 @@ static final class Chat1_19_3 {
static final @Nullable MethodHandle ACTUAL_GET_REGISTRY_ACCESS = SERVER_LEVEL_GET_REGISTRY_ACCESS == null ? LEVEL_GET_REGISTRY_ACCESS : SERVER_LEVEL_GET_REGISTRY_ACCESS;
static final @Nullable MethodHandle REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL = searchMethod(CLASS_REGISTRY_ACCESS, Modifier.PUBLIC, "registry", Optional.class, CLASS_RESOURCE_KEY);
static final @Nullable MethodHandle REGISTRY_GET_OPTIONAL = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getOptional", Optional.class, CLASS_RESOURCE_LOCATION);
static final @Nullable MethodHandle REGISTRY_GET_HOLDER = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getHolder", Optional.class, CLASS_RESOURCE_LOCATION);
static final @Nullable MethodHandle REGISTRY_GET_ID = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getId", int.class, Object.class);
static final @Nullable MethodHandle DISGUISED_CHAT_PACKET_CONSTRUCTOR;
static final @Nullable MethodHandle CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR;
static final @Nullable MethodHandle CHAT_TYPE_BOUND_CONSTRUCTOR;

static final Object CHAT_TYPE_RESOURCE_KEY;

static {
MethodHandle boundNetworkConstructor = null;
MethodHandle boundConstructor = null;
MethodHandle disguisedChatPacketConstructor = null;
Object chatTypeResourceKey = null;

Expand All @@ -118,9 +127,27 @@ static final class Chat1_19_3 {
}
}

Class<?> classChatTypeBound = findClass(findMcClassName("network.chat.ChatType$BoundNetwork"));
if (classChatTypeBound == null) {
final Class<?> parentClass = findClass(findMcClassName("network.chat.ChatMessageType"));
if (parentClass != null) {
for (final Class<?> childClass : parentClass.getClasses()) {
boundConstructor = findConstructor(childClass, CLASS_HOLDER, CLASS_CHAT_COMPONENT, Optional.class);
if (boundConstructor != null) {
classChatTypeBound = childClass;
break;
}
}
}
}

final Class<?> disguisedChatPacketClass = findClass(findMcClassName("network.protocol.game.ClientboundDisguisedChatPacket"));
if (disguisedChatPacketClass != null && classChatTypeBoundNetwork != null) {
disguisedChatPacketConstructor = findConstructor(disguisedChatPacketClass, CLASS_CHAT_COMPONENT, classChatTypeBoundNetwork);
if (disguisedChatPacketClass != null) {
if (classChatTypeBoundNetwork != null) {
disguisedChatPacketConstructor = findConstructor(disguisedChatPacketClass, CLASS_CHAT_COMPONENT, classChatTypeBoundNetwork);
} else if (classChatTypeBound != null) {
disguisedChatPacketConstructor = findConstructor(disguisedChatPacketClass, CLASS_CHAT_COMPONENT, classChatTypeBound);
}
}

if (NEW_RESOURCE_LOCATION != null && RESOURCE_KEY_CREATE != null) {
Expand All @@ -135,14 +162,15 @@ static final class Chat1_19_3 {

DISGUISED_CHAT_PACKET_CONSTRUCTOR = disguisedChatPacketConstructor;
CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR = boundNetworkConstructor;
CHAT_TYPE_BOUND_CONSTRUCTOR = boundConstructor;
CHAT_TYPE_RESOURCE_KEY = chatTypeResourceKey;
}

private Chat1_19_3() {
}

static boolean isSupported() {
return ACTUAL_GET_REGISTRY_ACCESS != null && REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL != null && REGISTRY_GET_OPTIONAL != null && CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR != null && DISGUISED_CHAT_PACKET_CONSTRUCTOR != null && CHAT_TYPE_RESOURCE_KEY != null;
return ACTUAL_GET_REGISTRY_ACCESS != null && REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL != null && REGISTRY_GET_OPTIONAL != null && (CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR != null || CHAT_TYPE_BOUND_CONSTRUCTOR != null) && DISGUISED_CHAT_PACKET_CONSTRUCTOR != null && CHAT_TYPE_RESOURCE_KEY != null;
}
}

Expand Down Expand Up @@ -238,4 +266,58 @@ static boolean isSupported() {
return NEW_CLIENTBOUND_ENTITY_SOUND != null && SOUND_EVENT_REGISTRY != null && NEW_RESOURCE_LOCATION != null && REGISTRY_GET_OPTIONAL != null && REGISTRY_WRAP_AS_HOLDER != null && SOUND_EVENT_CREATE_VARIABLE_RANGE != null;
}
}

static final class Book_1_20_5 {
static final Class<?> CLASS_CRAFT_ITEMSTACK = findCraftClass("inventory.CraftItemStack");
static final Class<?> CLASS_MC_ITEMSTACK = findMcClass("world.item.ItemStack");
static final Class<?> CLASS_MC_DATA_COMPONENT_TYPE = findMcClass("core.component.DataComponentType");
static final Class<?> CLASS_MC_BOOK_CONTENT = findMcClass("world.item.component.WrittenBookContent");
static final Class<?> CLASS_MC_FILTERABLE = findMcClass("server.network.Filterable");
static final Class<?> CLASS_CRAFT_REGISTRY = findCraftClass("CraftRegistry");
static final MethodHandle CREATE_FILTERABLE = searchMethod(CLASS_MC_FILTERABLE, Modifier.PUBLIC | Modifier.STATIC, "passThrough", CLASS_MC_FILTERABLE, Object.class);
static final MethodHandle GET_REGISTRY = findStaticMethod(CLASS_CRAFT_REGISTRY, "getMinecraftRegistry", CLASS_REGISTRY, CLASS_RESOURCE_KEY);
static final MethodHandle CREATE_REGISTRY_KEY = searchMethod(CLASS_RESOURCE_KEY, Modifier.PUBLIC | Modifier.STATIC, "createRegistryKey", CLASS_RESOURCE_KEY, CLASS_RESOURCE_LOCATION);
static final MethodHandle NEW_RESOURCE_LOCATION = findConstructor(CLASS_RESOURCE_LOCATION, String.class, String.class);
static final MethodHandle NEW_BOOK_CONTENT = findConstructor(CLASS_MC_BOOK_CONTENT, CLASS_MC_FILTERABLE, String.class, Integer.TYPE, List.class, Boolean.TYPE);
static final MethodHandle REGISTRY_GET_OPTIONAL = searchMethod(CLASS_REGISTRY, Modifier.PUBLIC, "getOptional", Optional.class, CLASS_RESOURCE_LOCATION);
static final Class<?> CLASS_ENUM_HAND = findClass(
findNmsClassName("EnumHand"),
findMcClassName("world.EnumHand"),
findMcClassName("world.InteractionHand")
);
static final Object HAND_MAIN = findEnum(CLASS_ENUM_HAND, "MAIN_HAND", 0);
static final MethodHandle MC_ITEMSTACK_SET = searchMethod(CLASS_MC_ITEMSTACK, Modifier.PUBLIC, "set", Object.class, CLASS_MC_DATA_COMPONENT_TYPE, Object.class);
static final MethodHandle CRAFT_ITEMSTACK_NMS_COPY = findStaticMethod(CLASS_CRAFT_ITEMSTACK, "asNMSCopy", CLASS_MC_ITEMSTACK, ItemStack.class);
static final MethodHandle CRAFT_ITEMSTACK_CRAFT_MIRROR = findStaticMethod(CLASS_CRAFT_ITEMSTACK, "asCraftMirror", CLASS_CRAFT_ITEMSTACK, CLASS_MC_ITEMSTACK);
static final Object WRITTEN_BOOK_COMPONENT_TYPE;
static final Class<?> PACKET_OPEN_BOOK = findClass(
findMcClassName("network.protocol.game.PacketPlayOutOpenBook"),
findMcClassName("network.protocol.game.ClientboundOpenBookPacket")
);
static final MethodHandle NEW_PACKET_OPEN_BOOK = findConstructor(PACKET_OPEN_BOOK, CLASS_ENUM_HAND);

static {
Object componentTypeRegistry = null;
Object componentType = null;
try {
if (GET_REGISTRY != null && CREATE_REGISTRY_KEY != null && NEW_RESOURCE_LOCATION != null && REGISTRY_GET_OPTIONAL != null) {
final Object registryKey = CREATE_REGISTRY_KEY.invoke(NEW_RESOURCE_LOCATION.invoke("minecraft", "data_component_type"));
try {
componentTypeRegistry = GET_REGISTRY.invoke(registryKey);
} catch (final Exception ignored) {
}
if (componentTypeRegistry != null) {
componentType = ((Optional<?>) REGISTRY_GET_OPTIONAL.invoke(componentTypeRegistry, NEW_RESOURCE_LOCATION.invoke("minecraft", "written_book_content"))).orElse(null);
}
}
} catch (final Throwable error) {
logError(error, "Failed to initialize Book_1_20_5 CraftBukkit facet");
}
WRITTEN_BOOK_COMPONENT_TYPE = componentType;
}

static boolean isSupported() {
return WRITTEN_BOOK_COMPONENT_TYPE != null && CREATE_FILTERABLE != null && NEW_BOOK_CONTENT != null && CRAFT_ITEMSTACK_NMS_COPY != null && MC_ITEMSTACK_SET != null && CRAFT_ITEMSTACK_CRAFT_MIRROR != null && NEW_PACKET_OPEN_BOOK != null && HAND_MAIN != null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -316,17 +316,23 @@ public void sendMessage(final @NotNull CommandSender viewer, final @NotNull Iden
} else {
final ChatType.Bound bound = (ChatType.Bound) type;
try {
final Object nameComponent = this.createMessage(viewer, bound.name());
final Object targetComponent = bound.target() != null ? this.createMessage(viewer, bound.target()) : null;
final Object registryAccess = CraftBukkitAccess.Chat1_19_3.ACTUAL_GET_REGISTRY_ACCESS.invoke(CraftBukkitAccess.Chat1_19_3.SERVER_PLAYER_GET_LEVEL.invoke(CRAFT_PLAYER_GET_HANDLE.invoke(viewer)));
final Object chatTypeRegistry = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_ACCESS_GET_REGISTRY_OPTIONAL.invoke(registryAccess, CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_RESOURCE_KEY)).orElseThrow(NoSuchElementException::new);
final Object typeResourceLocation = CraftBukkitAccess.Chat1_19_3.NEW_RESOURCE_LOCATION.invoke(bound.type().key().namespace(), bound.type().key().value());
final Object chatTypeObject = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_OPTIONAL.invoke(chatTypeRegistry, typeResourceLocation)).orElseThrow(NoSuchElementException::new);
final int networkId = (int) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_ID.invoke(chatTypeRegistry, chatTypeObject);
if (networkId < 0) {
throw new IllegalArgumentException("Could not get a valid network id from " + type);
final Object boundNetwork;
if (CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR != null) {
final Object chatTypeObject = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_OPTIONAL.invoke(chatTypeRegistry, typeResourceLocation)).orElseThrow(NoSuchElementException::new);
final int networkId = (int) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_ID.invoke(chatTypeRegistry, chatTypeObject);
if (networkId < 0) {
throw new IllegalArgumentException("Could not get a valid network id from " + type);
}
boundNetwork = CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR.invoke(networkId, nameComponent, targetComponent);
} else {
final Object chatTypeHolder = ((Optional<?>) CraftBukkitAccess.Chat1_19_3.REGISTRY_GET_HOLDER.invoke(chatTypeRegistry, typeResourceLocation)).orElseThrow(NoSuchElementException::new);
boundNetwork = CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_BOUND_CONSTRUCTOR.invoke(chatTypeHolder, nameComponent, Optional.ofNullable(targetComponent));
}
final Object nameComponent = this.createMessage(viewer, bound.name());
final Object targetComponent = bound.target() != null ? this.createMessage(viewer, bound.target()) : null;
final Object boundNetwork = CraftBukkitAccess.Chat1_19_3.CHAT_TYPE_BOUND_NETWORK_CONSTRUCTOR.invoke(networkId, nameComponent, targetComponent);

this.sendMessage(viewer, CraftBukkitAccess.Chat1_19_3.DISGUISED_CHAT_PACKET_CONSTRUCTOR.invoke(message, boundNetwork));
} catch (final Throwable error) {
Expand Down Expand Up @@ -779,6 +785,45 @@ public void resetTitle(final @NotNull Player viewer) {
}
}

static final class Book_1_20_5 extends PacketFacet<Player> implements Facet.Book<Player, Object, ItemStack> {
@Override
public boolean isSupported() {
return super.isSupported() && CraftBukkitAccess.Book_1_20_5.isSupported();
}

@Override
public @Nullable ItemStack createBook(final @NotNull String title, final @NotNull String author, final @NotNull Iterable<Object> pages) {
try {
final ItemStack item = new ItemStack(Material.WRITTEN_BOOK);
final List<Object> pageList = new ArrayList<>();
for (final Object page : pages) {
pageList.add(CraftBukkitAccess.Book_1_20_5.CREATE_FILTERABLE.invoke(page));
}
final Object bookContent = CraftBukkitAccess.Book_1_20_5.NEW_BOOK_CONTENT.invoke(CraftBukkitAccess.Book_1_20_5.CREATE_FILTERABLE.invoke(title), author, 0, pageList, true);
final Object stack = CraftBukkitAccess.Book_1_20_5.CRAFT_ITEMSTACK_NMS_COPY.invoke(item);
CraftBukkitAccess.Book_1_20_5.MC_ITEMSTACK_SET.invoke(stack, CraftBukkitAccess.Book_1_20_5.WRITTEN_BOOK_COMPONENT_TYPE, bookContent);
return (ItemStack) CraftBukkitAccess.Book_1_20_5.CRAFT_ITEMSTACK_CRAFT_MIRROR.invoke(stack);
} catch (final Throwable error) {
logError(error, "Failed to apply written_book_content component to ItemStack");
}
return null;
}

@Override
public void openBook(final @NotNull Player viewer, final @NotNull ItemStack book) {
final PlayerInventory inventory = viewer.getInventory();
final ItemStack current = inventory.getItemInHand();
try {
inventory.setItemInHand(book);
this.sendMessage(viewer, CraftBukkitAccess.Book_1_20_5.NEW_PACKET_OPEN_BOOK.invoke(CraftBukkitAccess.Book_1_20_5.HAND_MAIN));
} catch (final Throwable error) {
logError(error, "Failed to send openBook packet: %s", book);
} finally {
inventory.setItemInHand(current);
}
}
}

protected static abstract class AbstractBook extends PacketFacet<Player> implements Facet.Book<Player, Object, ItemStack> {
protected static final int HAND_MAIN = 0;
private static final Material BOOK_TYPE = (Material) findEnum(Material.class, "WRITTEN_BOOK");
Expand Down Expand Up @@ -1412,7 +1457,7 @@ private static MethodHandle first(final MethodHandle... handles) {

@Override
public boolean isSupported() {
return (CLIENTBOUND_TAB_LIST_PACKET_CTOR != null || CLIENTBOUND_TAB_LIST_PACKET_CTOR_PRE_1_17 != null) && CLIENTBOUND_TAB_LIST_PACKET_SET_HEADER != null && CLIENTBOUND_TAB_LIST_PACKET_SET_FOOTER != null && super.isSupported();
return (CLIENTBOUND_TAB_LIST_PACKET_CTOR != null || (CLIENTBOUND_TAB_LIST_PACKET_CTOR_PRE_1_17 != null && CLIENTBOUND_TAB_LIST_PACKET_SET_HEADER != null && CLIENTBOUND_TAB_LIST_PACKET_SET_FOOTER != null)) && super.isSupported();
}

protected Object create117Packet(final Player viewer, final @Nullable Object header, final @Nullable Object footer) throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.lang.invoke.MethodHandles.insertArguments;
import static net.kyori.adventure.platform.bukkit.BukkitComponentSerializer.gson;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findCraftClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findMcClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findNmsClass;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findNmsClassName;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.findStaticMethod;
import static net.kyori.adventure.platform.bukkit.MinecraftReflection.lookup;

/**
Expand Down Expand Up @@ -89,6 +92,12 @@ public static boolean isSupported() {
findMcClassName("network.chat.IChatBaseComponent"),
findMcClassName("network.chat.Component")
);
private static final @Nullable Class<?> CLASS_CRAFT_REGISTRY = findCraftClass("CraftRegistry");
private static final @Nullable Class<?> CLASS_REGISTRY_ACCESS = findClass(
findMcClassName("core.IRegistryCustom"),
findMcClassName("core.RegistryAccess")
);
private static final @Nullable MethodHandle GET_REGISTRY = findStaticMethod(CLASS_CRAFT_REGISTRY, "getMinecraftRegistry", CLASS_REGISTRY_ACCESS);
private static final AtomicReference<RuntimeException> INITIALIZATION_ERROR = new AtomicReference<>(new UnsupportedOperationException());

private static final Object MC_TEXT_GSON;
Expand All @@ -106,6 +115,7 @@ public static boolean isSupported() {

try {
if (CLASS_CHAT_COMPONENT != null) {
final Object registryAccess = GET_REGISTRY != null ? GET_REGISTRY.invoke() : null;
// Chat serializer //
final Class<?> chatSerializerClass = Arrays.stream(CLASS_CHAT_COMPONENT.getClasses())
.filter(c -> {
Expand Down Expand Up @@ -164,6 +174,22 @@ public static boolean isSupported() {
.filter(m -> m.getParameterCount() == 1 && CLASS_CHAT_COMPONENT.isAssignableFrom(m.getParameterTypes()[0]))
.findFirst()
.orElse(null);
final Method deserializeTreeWithRegistryAccess = Arrays.stream(declaredMethods)
.filter(m -> Modifier.isStatic(m.getModifiers()))
.filter(m -> CLASS_CHAT_COMPONENT.isAssignableFrom(m.getReturnType()))
.filter(m -> m.getParameterCount() == 2)
.filter(m -> m.getParameterTypes()[0].equals(JsonElement.class))
.filter(m -> m.getParameterTypes()[1].isInstance(registryAccess))
.findFirst()
.orElse(null);
final Method serializeTreeWithRegistryAccess = Arrays.stream(declaredMethods)
.filter(m -> Modifier.isStatic(m.getModifiers()))
.filter(m -> m.getReturnType().equals(JsonElement.class))
.filter(m -> m.getParameterCount() == 2)
.filter(m -> CLASS_CHAT_COMPONENT.isAssignableFrom(m.getParameterTypes()[0]))
.filter(m -> m.getParameterTypes()[1].isInstance(registryAccess))
.findFirst()
.orElse(null);
if (deserialize != null) {
textSerializerDeserialize = lookup().unreflect(deserialize);
}
Expand All @@ -172,9 +198,15 @@ public static boolean isSupported() {
}
if (deserializeTree != null) {
textSerializerDeserializeTree = lookup().unreflect(deserializeTree);
} else if (deserializeTreeWithRegistryAccess != null) {
deserializeTreeWithRegistryAccess.setAccessible(true);
textSerializerDeserializeTree = insertArguments(lookup().unreflect(deserializeTreeWithRegistryAccess), 1, registryAccess);
}
if (serializeTree != null) {
textSerializerSerializeTree = lookup().unreflect(serializeTree);
} else if (serializeTreeWithRegistryAccess != null) {
serializeTreeWithRegistryAccess.setAccessible(true);
textSerializerSerializeTree = insertArguments(lookup().unreflect(serializeTreeWithRegistryAccess), 1, registryAccess);
}
}
}
Expand Down