diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02bc15cc..e5cc9810 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,22 @@ [versions] jetbrains-annotations = "26.0.2" -# Do not update Spigot/Paper to 1.21, InventoryView changed from class to interface type -# Dynamic Title Update feature throws IncompatibleClassChangeError due to that -# Feel free to implement a workaround on InventoryUpdate.java if u need to use a 1.21+ specific API -spigot = "1.20.6-R0.1-SNAPSHOT" -paperSpigot = "1.20.6-R0.1-SNAPSHOT" +# InventoryView changed from a class to an interface type in 1.21. That used to throw +# IncompatibleClassChangeError on the Dynamic Title Update feature when compiled against an +# older (class-based) InventoryView while running on a newer (interface-based) runtime. +# This is now handled by McVersion#isModern() branching in InventoryUpdate.java, ReflectionUtils.java +# and AnvilInputNMS.java, and by compiling inventory-framework-platform-bukkit/-paper against a +# matching modern Spigot/Paper artifact (see the versions below) instead of the old pinned 1.20.6 one. +# inventory-framework-platform-paper also requires a Java 25 toolchain, since Paper's own published +# artifacts for this version declare that requirement (see its build.gradle.kts). +spigot = "26.1-R0.1-SNAPSHOT" +paperSpigot = "26.1.2.build.+" junit = "6.0.0" mockito = "4.11.0" adventure-api = "4.25.0" -kotlin = "1.9.20" +kotlin = "2.3.21" plugin-shadowjar = "8.3.8" plugin-spotless = "7.2.1" -plugin-bukkit = "0.7.1" +plugin-bukkit = "0.8.0" minestom = "b39badc77b" folialib = "0.5.1" diff --git a/inventory-framework-anvil-input/src/main/java/me/devnatan/inventoryframework/AnvilInputNMS.java b/inventory-framework-anvil-input/src/main/java/me/devnatan/inventoryframework/AnvilInputNMS.java index 7fe7f5d7..60d66e28 100644 --- a/inventory-framework-anvil-input/src/main/java/me/devnatan/inventoryframework/AnvilInputNMS.java +++ b/inventory-framework-anvil-input/src/main/java/me/devnatan/inventoryframework/AnvilInputNMS.java @@ -49,31 +49,53 @@ class AnvilInputNMS { static { try { + final boolean modern = McVersion.isModern(); + ANVIL = Objects.requireNonNull( - getNMSClass("world.inventory", "ContainerAnvil"), "ContainerAnvil NMS class not found"); + getNMSClass("world.inventory", modern ? "AnvilMenu" : "ContainerAnvil"), + "AnvilMenu/ContainerAnvil NMS class not found"); - final Class playerInventoryClass = getNMSClass("world.entity.player", "PlayerInventory"); + final Class playerInventoryClass = + getNMSClass("world.entity.player", modern ? "Inventory" : "PlayerInventory"); ANVIL_CONSTRUCTOR = getConstructor(ANVIL, int.class, playerInventoryClass); CONTAINER_CHECK_REACHABLE = setFieldHandle(CONTAINER, boolean.class, "checkReachable"); - final Class containerPlayer = getNMSClass("world.inventory", "ContainerPlayer"); + final Class containerPlayer = + getNMSClass("world.inventory", modern ? "InventoryMenu" : "ContainerPlayer"); PLAYER_DEFAULT_CONTAINER = getField(ENTITY_PLAYER, containerPlayer, "inventoryMenu", "bQ", "bR"); - final String activeContainerObfuscatedName = ReflectionUtils.supportsMC1202() ? "bS" : "bR"; + final String activeContainerFieldName = + modern ? "containerMenu" : (ReflectionUtils.supportsMC1202() ? "bS" : "bR"); SET_PLAYER_ACTIVE_CONTAINER = setField( - ENTITY_PLAYER, containerPlayer, "activeContainer", "containerMenu", activeContainerObfuscatedName); + ENTITY_PLAYER, + containerPlayer, + modern ? "containerMenu" : "activeContainer", + "containerMenu", + activeContainerFieldName); GET_PLAYER_NEXT_CONTAINER_COUNTER = getMethod(ENTITY_PLAYER, "nextContainerCounter", MethodType.methodType(int.class)); - GET_PLAYER_INVENTORY = getMethod( - ENTITY_PLAYER, "fN", MethodType.methodType(playerInventoryClass), false, "fR", "getInventory"); - - CONTAINER_WINDOW_ID = setField(CONTAINER, int.class, "windowId", "containerId", "j"); + GET_PLAYER_INVENTORY = modern + ? getMethod(ENTITY_PLAYER, "getInventory", MethodType.methodType(playerInventoryClass)) + : getMethod( + ENTITY_PLAYER, + "fN", + MethodType.methodType(playerInventoryClass), + false, + "fR", + "getInventory"); + + CONTAINER_WINDOW_ID = + setField(CONTAINER, int.class, modern ? "containerId" : "windowId", "containerId", "j"); + + final Class slotListenerClass = + getNMSClass("world.inventory", modern ? "ContainerListener" : "ICrafting"); ADD_CONTAINER_SLOT_LISTENER = getMethod( - CONTAINER, "a", MethodType.methodType(void.class, getNMSClass("world.inventory.ICrafting"))); - INIT_MENU = getMethod(ENTITY_PLAYER, "a", MethodType.methodType(void.class, CONTAINER)); + CONTAINER, modern ? "addSlotListener" : "a", MethodType.methodType(void.class, slotListenerClass)); + INIT_MENU = + getMethod(ENTITY_PLAYER, modern ? "initMenu" : "a", MethodType.methodType(void.class, CONTAINER)); GET_TOP_INVENTORY = getMethod(InventoryView.class, "getTopInventory", MethodType.methodType(Inventory.class)); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate.java index 49791a03..ff763869 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/InventoryUpdate.java @@ -71,22 +71,40 @@ public final class InventoryUpdate { public static final Object[] DUMMY_COLOR_MODIFIERS = new Object[0]; static { + final boolean modern = McVersion.isModern(); + // Initialize classes. CHAT_MESSAGE = SUPPORTS_19 ? null : ReflectionUtils.getNMSClass("network.chat", "ChatMessage"); - PACKET_PLAY_OUT_OPEN_WINDOW = ReflectionUtils.getNMSClass("network.protocol.game", "PacketPlayOutOpenWindow"); - I_CHAT_BASE_COMPONENT = ReflectionUtils.getNMSClass("network.chat", "IChatBaseComponent"); + PACKET_PLAY_OUT_OPEN_WINDOW = modern + ? ReflectionUtils.getNMSClass("network.protocol.game", "ClientboundOpenScreenPacket") + : ReflectionUtils.getNMSClass("network.protocol.game", "PacketPlayOutOpenWindow"); + I_CHAT_BASE_COMPONENT = modern + ? ReflectionUtils.getNMSClass("network.chat", "Component") + : ReflectionUtils.getNMSClass("network.chat", "IChatBaseComponent"); + CONTAINER = modern + ? ReflectionUtils.getNMSClass("world.inventory", "AbstractContainerMenu") + : ReflectionUtils.getNMSClass("world.inventory", "Container"); // Check if we use containers, otherwise, can throw errors on older versions. - CONTAINERS = useContainers() ? ReflectionUtils.getNMSClass("world.inventory", "Containers") : null; - CONTAINER = ReflectionUtils.getNMSClass("world.inventory", "Container"); - I_CHAT_MUTABLE_COMPONENT = - SUPPORTS_19 ? ReflectionUtils.getNMSClass("network.chat", "IChatMutableComponent") : null; + CONTAINERS = useContainers() + ? (modern + ? ReflectionUtils.getNMSClass("world.inventory", "MenuType") + : ReflectionUtils.getNMSClass("world.inventory", "Containers")) + : null; + I_CHAT_MUTABLE_COMPONENT = SUPPORTS_19 + ? (modern + ? ReflectionUtils.getNMSClass("network.chat", "MutableComponent") + : ReflectionUtils.getNMSClass("network.chat", "IChatMutableComponent")) + : null; MINECRAFT_MENU_TYPE = getClassOrNull("net.minecraft.world.inventory.MenuType"); // Initialize methods. getBukkitView = getMethod(CONTAINER, "getBukkitView", MethodType.methodType(InventoryView.class)); literal = SUPPORTS_19 ? getMethod( - I_CHAT_BASE_COMPONENT, "b", MethodType.methodType(I_CHAT_MUTABLE_COMPONENT, String.class), true) + I_CHAT_BASE_COMPONENT, + modern ? "literal" : "b", + MethodType.methodType(I_CHAT_MUTABLE_COMPONENT, String.class), + true) : null; final Class paperAdventure = getClassOrNull("io.papermc.paper.adventure.PaperAdventure"); @@ -109,9 +127,10 @@ public final class InventoryUpdate { getConstructor(PACKET_PLAY_OUT_OPEN_WINDOW, int.class, String.class, I_CHAT_BASE_COMPONENT, int.class); // Initialize fields. - activeContainer = - getField(ENTITY_PLAYER, CONTAINER, "activeContainer", "bR", "bV", "bW", "bU", "bP", "containerMenu"); - windowId = getField(CONTAINER, int.class, "windowId", "j", "containerId"); + activeContainer = modern + ? getField(ENTITY_PLAYER, CONTAINER, "containerMenu") + : getField(ENTITY_PLAYER, CONTAINER, "activeContainer", "bR", "bV", "bW", "bU", "bP", "containerMenu"); + windowId = getField(CONTAINER, int.class, modern ? "containerId" : "windowId", "j", "containerId"); } private static Class getClassOrNull(String className) { @@ -177,7 +196,12 @@ public static void updateInventory(Player player, Object newTitle) { if (container == null) return; // If the container was added in a newer version than the current, return. - if (container.getContainerVersion() > ReflectionUtils.MINOR_NUMBER && useContainers()) { + // Only applies on 1.x; on the year-based scheme (26+) MINOR_NUMBER is the release's own + // minor (e.g. 1 for 26.1.x) and is unrelated to the container-added-in-1.x versions below, + // so all containers are considered valid there. + if (McVersion.current().getMajor() == 1 + && container.getContainerVersion() > ReflectionUtils.MINOR_NUMBER + && useContainers()) { return; } @@ -344,7 +368,7 @@ public static MethodHandle getMethod( * @return whether to use containers. */ public static boolean useContainers() { - return ReflectionUtils.MINOR_NUMBER > 13; + return McVersion.isModern() || ReflectionUtils.MINOR_NUMBER > 13; } /** @@ -426,6 +450,8 @@ public Object getObject() { String name = (version == 14 && this == CARTOGRAPHY_TABLE) ? "CARTOGRAPHY" : name(); // Since 1.17, containers go from "a" to "x". if (version > 16 && version <= 20) name = String.valueOf(alphabet[ordinal()]); + // Modern (26+): MenuType fields use lowercase 'x', e.g. GENERIC_9x3. + if (McVersion.isModern()) name = name.replace("X", "x"); Field field = CONTAINERS.getField(name); return field.get(null); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/McVersion.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/McVersion.java index 56f41560..276e1f13 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/McVersion.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/McVersion.java @@ -143,4 +143,13 @@ public static boolean supports(int minorNumber) { public static boolean supports(int minorNumber, int patchNumber) { return CURRENT_VERSION.isAtLeast(1, minorNumber, patchNumber); } + + /** + * Whether the server is running a "modern" Mojang-mapped NMS naming scheme (1.17+, including + * the year-based versioning scheme introduced afterwards, e.g. 26.x), as opposed to the legacy + * obfuscated/versioned CraftBukkit naming scheme used prior to 1.17. + */ + public static boolean isModern() { + return CURRENT_VERSION.getMajor() > 1 || CURRENT_VERSION.getMinor() >= 17; + } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/ReflectionUtils.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/ReflectionUtils.java index 7ced2ad4..b8d11d58 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/ReflectionUtils.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/thirdparty/ReflectionUtils.java @@ -163,31 +163,36 @@ public static String getVersionInformation() { /** * The class for the NMS EntityPlayer. + * + * @see net.minecraft.server.level.ServerPlayer */ public static final Class ENTITY_PLAYER; static { - ENTITY_PLAYER = getNMSClass("server.level", "EntityPlayer"); + final boolean modern = McVersion.isModern(); + ENTITY_PLAYER = getNMSClass("server.level", modern ? "ServerPlayer" : "EntityPlayer"); CRAFT_PLAYER = getCraftClass("entity.CraftPlayer"); - Class playerConnection = getNMSClass("server.network", "PlayerConnection"); + Class playerConnection = + getNMSClass("server.network", modern ? "ServerGamePacketListenerImpl" : "PlayerConnection"); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle sendPacket = null, getHandle = null, connection = null; try { - connection = lookup.findGetter( - ENTITY_PLAYER, - v(21, 3, "f").v(20, "c").v(17, "b").v(21, 6, "g").orElse("playerConnection"), - playerConnection); + String connectionFieldName = modern + ? "connection" + : v(21, 3, "f").v(20, "c").v(17, "b").v(21, 6, "g").orElse("playerConnection"); + connection = lookup.findGetter(ENTITY_PLAYER, connectionFieldName, playerConnection); getHandle = lookup.findVirtual(CRAFT_PLAYER, "getHandle", MethodType.methodType(ENTITY_PLAYER)); final VersionHandler methodVersion = v(21, "send").v(20, "b").v(18, "a"); + String sendMethodName = modern ? "send" : methodVersion.orElse("sendPacket"); sendPacket = lookup.findVirtual( playerConnection, - methodVersion.orElse("sendPacket"), + sendMethodName, MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); diff --git a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.kt b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.kt index 9d5f6bed..c06c2b32 100644 --- a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.kt +++ b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.kt @@ -20,7 +20,8 @@ class GlobalClickInterceptor : PipelineInterceptor { // inherit cancellation so we can un-cancel it subject.isCancelled = - event.isCancelled || subject.config.isOptionSet(ViewConfig.CANCEL_ON_CLICK, true) + event.isCancelled || + subject.config.isOptionSet(ViewConfig.CANCEL_ON_CLICK, true) subject.root.onClick(subject) } } diff --git a/inventory-framework-platform-paper/build.gradle.kts b/inventory-framework-platform-paper/build.gradle.kts index 8c413101..38d8eef6 100644 --- a/inventory-framework-platform-paper/build.gradle.kts +++ b/inventory-framework-platform-paper/build.gradle.kts @@ -13,7 +13,11 @@ dependencies { } java { - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } } tasks.shadowJar { diff --git a/settings.gradle.kts b/settings.gradle.kts index 05585a2b..4cf6875d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,12 @@ pluginManagement { includeBuild("build-logic") } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { mavenCentral() maven("https://jitpack.io")