Skip to content

Commit

Permalink
Merge pull request #163 from 56738/spigot-1.20.5
Browse files Browse the repository at this point in the history
Bukkit 1.20.5 support
  • Loading branch information
kashike committed Apr 28, 2024
2 parents 94a37fb + 1557d3c commit d5ab099
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 11 deletions.
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

0 comments on commit d5ab099

Please sign in to comment.