diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 671a7535..fc422adf 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -92,6 +92,10 @@ updates:
directory: "/nms/1_20_5"
schedule:
interval: "daily"
+ - package-ecosystem: "maven"
+ directory: "/nms/1_20_6"
+ schedule:
+ interval: "daily"
- package-ecosystem: "maven"
directory: "/nms/abstraction"
schedule:
diff --git a/.github/workflows/auto-deploy.yml b/.github/workflows/auto-deploy.yml
index 0eda366c..07a0ae63 100644
--- a/.github/workflows/auto-deploy.yml
+++ b/.github/workflows/auto-deploy.yml
@@ -75,7 +75,7 @@ jobs:
mvn paper-nms:init -pl nms/1_20_1
mvn paper-nms:init -pl nms/1_20_2
mvn paper-nms:init -pl nms/1_20_3-4
- - name: Run BuildTools 1.20.5
+ - name: 'Run BuildTools 1.20.5 & 1.20.6'
if: steps.cache-paperclip-jars.outputs.cache-hit != 'true'
run: |
mkdir -p build
@@ -103,6 +103,7 @@ jobs:
cd ..
java -jar BuildTools.jar --remapped --disable-java-check --dont-update
+ java -jar BuildTools.jar --rev 1.20.6 --remapped --disable-java-check
cd ../
- name: Setup GPG keys
run: cat <(echo -e "${{ secrets.GPG_KEY }}") | gpg --batch --import
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index e5ee2553..3ece9364 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -81,7 +81,7 @@ jobs:
mvn paper-nms:init -pl nms/1_20_1
mvn paper-nms:init -pl nms/1_20_2
mvn paper-nms:init -pl nms/1_20_3-4
- - name: Run BuildTools 1.20.5
+ - name: 'Run BuildTools 1.20.5 & 1.20.6'
if: steps.cache-paperclip-jars.outputs.cache-hit != 'true'
run: |
mkdir -p build
@@ -109,6 +109,7 @@ jobs:
cd ..
java -jar BuildTools.jar --remapped --disable-java-check --dont-update
+ java -jar BuildTools.jar --rev 1.20.6 --remapped --disable-java-check
cd ../
- name: Build with Maven
run: mvn -B package --file pom.xml
diff --git a/IF/pom.xml b/IF/pom.xml
index 158faa0e..1ef5bdd1 100644
--- a/IF/pom.xml
+++ b/IF/pom.xml
@@ -161,6 +161,12 @@
${project.version}
compile
+
+ com.github.stefvanschie.inventoryframework
+ 1_20_6
+ ${project.version}
+ compile
+
org.spigotmc
spigot-api
diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/Version.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/Version.java
index 32ce41d7..64568eae 100644
--- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/Version.java
+++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/Version.java
@@ -153,14 +153,21 @@ public enum Version {
*
* @since 0.10.14
*/
- V1_20_5;
+ V1_20_5,
+
+ /**
+ * Version 1.20.6
+ *
+ * @since 0.10.14
+ */
+ V1_20_6;
/**
* A collection of versions on which modern smithing tables are available.
*/
private static final Collection MODERN_SMITHING_TABLE_VERSIONS = EnumSet.of(
V1_19_4,
- V1_20_0, V1_20_1, V1_20_2, V1_20_3_4, V1_20_5
+ V1_20_0, V1_20_1, V1_20_2, V1_20_3_4, V1_20_5, V1_20_6
);
/**
@@ -259,6 +266,8 @@ public static Version getVersion() {
return V1_20_3_4;
case "1.20.5":
return V1_20_5;
+ case "1.20.6":
+ return V1_20_6;
default:
throw new UnsupportedVersionException("The server version provided is not supported");
}
diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/VersionMatcher.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/VersionMatcher.java
index 39606800..f793eea1 100644
--- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/VersionMatcher.java
+++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/version/VersionMatcher.java
@@ -315,6 +315,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.AnvilInventoryImpl.class);
ANVIL_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.AnvilInventoryImpl.class);
+ ANVIL_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.AnvilInventoryImpl.class);
BEACON_INVENTORIES = new EnumMap<>(Version.class);
BEACON_INVENTORIES.put(Version.V1_14,
@@ -357,6 +359,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.BeaconInventoryImpl.class);
BEACON_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.BeaconInventoryImpl.class);
+ BEACON_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.BeaconInventoryImpl.class);
CARTOGRAPHY_TABLE_INVENTORIES = new EnumMap<>(Version.class);
CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_14,
@@ -399,6 +403,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.CartographyTableInventoryImpl.class);
CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.CartographyTableInventoryImpl.class);
+ CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.CartographyTableInventoryImpl.class);
ENCHANTING_TABLE_INVENTORIES = new EnumMap<>(Version.class);
ENCHANTING_TABLE_INVENTORIES.put(Version.V1_14,
@@ -441,6 +447,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.EnchantingTableInventoryImpl.class);
ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.EnchantingTableInventoryImpl.class);
+ ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.EnchantingTableInventoryImpl.class);
GRINDSTONE_INVENTORIES = new EnumMap<>(Version.class);
GRINDSTONE_INVENTORIES.put(Version.V1_14,
@@ -483,6 +491,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.GrindstoneInventoryImpl.class);
GRINDSTONE_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.GrindstoneInventoryImpl.class);
+ GRINDSTONE_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.GrindstoneInventoryImpl.class);
MERCHANT_INVENTORIES = new EnumMap<>(Version.class);
MERCHANT_INVENTORIES.put(Version.V1_14,
@@ -525,6 +535,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.MerchantInventoryImpl.class);
MERCHANT_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.MerchantInventoryImpl.class);
+ MERCHANT_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.MerchantInventoryImpl.class);
SMITHING_TABLE_INVENTORIES = new EnumMap<>(Version.class);
SMITHING_TABLE_INVENTORIES.put(Version.V1_19_4,
@@ -539,6 +551,8 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.SmithingTableInventoryImpl.class);
SMITHING_TABLE_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.SmithingTableInventoryImpl.class);
+ SMITHING_TABLE_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.SmithingTableInventoryImpl.class);
LEGACY_SMITHING_TABLE_INVENTORIES = new EnumMap<>(Version.class);
LEGACY_SMITHING_TABLE_INVENTORIES.put(Version.V1_16_1,
@@ -609,5 +623,7 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers
com.github.stefvanschie.inventoryframework.nms.v1_20_3.StonecutterInventoryImpl.class);
STONECUTTER_INVENTORIES.put(Version.V1_20_5,
com.github.stefvanschie.inventoryframework.nms.v1_20_5.StonecutterInventoryImpl.class);
+ STONECUTTER_INVENTORIES.put(Version.V1_20_6,
+ com.github.stefvanschie.inventoryframework.nms.v1_20_6.StonecutterInventoryImpl.class);
}
}
diff --git a/nms/1_20_6/pom.xml b/nms/1_20_6/pom.xml
new file mode 100644
index 00000000..c5dd0062
--- /dev/null
+++ b/nms/1_20_6/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ com.github.stefvanschie.inventoryframework
+ IF-parent
+ 0.10.14-SNAPSHOT
+ ../../pom.xml
+
+
+ 1_20_6
+
+
+ true
+
+
+
+
+ com.github.stefvanschie.inventoryframework
+ abstraction
+ ${project.version}
+ compile
+
+
+ org.spigotmc
+ spigot
+ 1.20.6-R0.1-SNAPSHOT
+ remapped-mojang
+ provided
+
+
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 2.0.3
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+ org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-mojang
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+ org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:csrg:maps-spigot
+ org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-obf
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/AnvilInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/AnvilInventoryImpl.java
new file mode 100644
index 00000000..65cfec65
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/AnvilInventoryImpl.java
@@ -0,0 +1,284 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.CustomInventoryUtil;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.AnvilMenu;
+import net.minecraft.world.inventory.ContainerLevelAccess;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.inventory.Slot;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Internal anvil inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class AnvilInventoryImpl extends AnvilInventory {
+
+ public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public Inventory openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 3) {
+ throw new IllegalArgumentException(
+ "The amount of items for an anvil should be 3, but is '" + itemAmount + "'"
+ );
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+
+ //ignore deprecation: superseding method is only available on Paper
+ //noinspection deprecation
+ CraftEventFactory.handleInventoryCloseEvent(serverPlayer);
+
+ serverPlayer.containerMenu = serverPlayer.inventoryMenu;
+
+ Component message = TextHolderUtil.toComponent(title);
+ ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(serverPlayer, message);
+
+ Inventory inventory = containerAnvil.getBukkitView().getTopInventory();
+
+ inventory.setItem(0, items[0]);
+ inventory.setItem(1, items[1]);
+ inventory.setItem(2, items[2]);
+
+ int containerId = containerAnvil.getContainerId();
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.ANVIL, message));
+ serverPlayer.containerMenu = containerAnvil;
+ serverPlayer.initMenu(containerAnvil);
+
+ return inventory;
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) {
+ NonNullList nmsItems = CustomInventoryUtil.convertToNMSItems(items);
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack cursor = CraftItemStack.asNMSCopy(player.getItemOnCursor());
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, cursor));
+ }
+
+ @Override
+ public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 0, nmsItem));
+ }
+
+ @Override
+ public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 1, nmsItem));
+ }
+
+ @Override
+ public void sendResultItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ sendResultItem(player, CraftItemStack.asNMSCopy(item));
+ }
+
+ @Override
+ public void clearResultItem(@NotNull Player player) {
+ sendResultItem(player, ItemStack.EMPTY);
+ }
+
+ @Override
+ public void setCursor(@NotNull Player player, @NotNull org.bukkit.inventory.ItemStack item) {
+ setCursor(player, CraftItemStack.asNMSCopy(item));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Sets the cursor of the given player
+ *
+ * @param player the player to set the cursor
+ * @param item the item to set the cursor to
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Deprecated
+ private void setCursor(@NotNull Player player, @NotNull ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, item));
+ }
+
+ /**
+ * Sends the result item to the specified player with the given item
+ *
+ * @param player the player to send the result item to
+ * @param item the result item
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Deprecated
+ private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 2, item));
+ }
+
+ /**
+ * Gets the container id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the container id for
+ * @return the container id
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Contract(pure = true)
+ @Deprecated
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @NotNull
+ @Contract(pure = true)
+ @Deprecated
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container anvil for responding to item renaming
+ *
+ * @since 0.10.14
+ */
+ private class ContainerAnvilImpl extends AnvilMenu {
+
+ /**
+ * Creates a new custom anvil container for the specified player
+ *
+ * @param serverPlayer the player for whom this anvil container is
+ * @param title the title of the inventory
+ * @since 0.10.14
+ */
+ public ContainerAnvilImpl(@NotNull ServerPlayer serverPlayer, @NotNull Component title) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory(),
+ ContainerLevelAccess.create(serverPlayer.getCommandSenderWorld(), new BlockPos(0, 0, 0)));
+
+ this.checkReachable = false;
+ this.cost.set(AnvilInventoryImpl.super.cost);
+
+ setTitle(title);
+
+ Slot originalSlot = this.slots.get(2);
+
+ this.slots.set(2, new Slot(originalSlot.container, originalSlot.index, originalSlot.x, originalSlot.y) {
+ @Override
+ public boolean mayPlace(@NotNull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public boolean mayPickup(net.minecraft.world.entity.player.@NotNull Player playerEntity) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {
+ originalSlot.onTake(player, stack);
+ }
+ });
+ }
+
+ @Override
+ public boolean setItemName(@Nullable String name) {
+ name = name == null ? "" : name;
+
+ /* Only update if the name is actually different. This may be called even if the name is not different,
+ particularly when putting an item in the first slot. */
+ if (!name.equals(AnvilInventoryImpl.super.observableText.get())) {
+ AnvilInventoryImpl.super.observableText.set(name);
+ }
+
+ //the client predicts the output result, so we broadcast the state again to override it
+ broadcastFullState();
+ return true; //no idea what this is for
+ }
+
+ @Override
+ public void createResult() {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.@NotNull Player nmsPlayer) {}
+
+ @Override
+ protected void clearContainer(net.minecraft.world.entity.player.@NotNull Player player,
+ @NotNull Container inventory) {}
+
+ @Override
+ protected void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {}
+
+ public int getContainerId() {
+ return this.containerId;
+ }
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/BeaconInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/BeaconInventoryImpl.java
new file mode 100644
index 00000000..8d2b3914
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/BeaconInventoryImpl.java
@@ -0,0 +1,200 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.BeaconMenu;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryBeacon;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+/**
+ * Internal beacon inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class BeaconInventoryImpl extends BeaconInventory {
+
+ public BeaconInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public void openInventory(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(serverPlayer, item);
+
+ serverPlayer.containerMenu = containerBeacon;
+
+ int id = containerBeacon.containerId;
+ Component beacon = Component.literal("Beacon");
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(id, MenuType.BEACON, beacon));
+
+ sendItem(player, item);
+ }
+
+ @Override
+ public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ NonNullList items = NonNullList.of(
+ ItemStack.EMPTY, //the first item doesn't count for some reason, so send a dummy item
+ CraftItemStack.asNMSCopy(item)
+ );
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack cursor = CraftItemStack.asNMSCopy(player.getItemOnCursor());
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, items, cursor));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Gets the container id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the container id for
+ * @return the container id
+ * @since 0.10.14
+ */
+ @Contract(pure = true)
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container beacon
+ *
+ * @since 0.10.14
+ */
+ private class ContainerBeaconImpl extends BeaconMenu {
+
+ /**
+ * The player for this beacon container
+ */
+ @NotNull
+ private final Player player;
+
+ /**
+ * The internal bukkit entity for this container beacon
+ */
+ @Nullable
+ private CraftInventoryView bukkitEntity;
+
+ /**
+ * Field for accessing the beacon field
+ */
+ @NotNull
+ private final Field beaconField;
+
+ public ContainerBeaconImpl(@NotNull ServerPlayer serverPlayer, @Nullable org.bukkit.inventory.ItemStack item) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory());
+
+ this.player = serverPlayer.getBukkitEntity();
+ setTitle(Component.empty());
+
+ try {
+ //noinspection JavaReflectionMemberAccess
+ this.beaconField = BeaconMenu.class.getDeclaredField("s"); //beacon
+ this.beaconField.setAccessible(true);
+ } catch (NoSuchFieldException exception) {
+ throw new RuntimeException(exception);
+ }
+
+ try {
+ ItemStack itemStack = CraftItemStack.asNMSCopy(item);
+
+ ((Container) beaconField.get(this)).setItem(0, itemStack);
+ } catch (IllegalAccessException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ @NotNull
+ @Override
+ public CraftInventoryView getBukkitView() {
+ if (bukkitEntity == null) {
+ try {
+ CraftInventory inventory = new CraftInventoryBeacon((Container) beaconField.get(this)) {
+ @NotNull
+ @Contract(pure = true)
+ @Override
+ public InventoryHolder getHolder() {
+ return inventoryHolder;
+ }
+ };
+
+ bukkitEntity = new CraftInventoryView(player, inventory, this);
+ } catch (IllegalAccessException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ return bukkitEntity;
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/CartographyTableInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/CartographyTableInventoryImpl.java
new file mode 100644
index 00000000..0e9624f2
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/CartographyTableInventoryImpl.java
@@ -0,0 +1,218 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.CustomInventoryUtil;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.CartographyTableMenu;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryCartography;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+/**
+ * Internal cartography table inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class CartographyTableInventoryImpl extends CartographyTableInventory {
+
+ public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public void openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 3) {
+ throw new IllegalArgumentException(
+ "The amount of items for a cartography table should be 3, but is '" + itemAmount + "'"
+ );
+ }
+
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ Component message = TextHolderUtil.toComponent(title);
+
+ ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl(
+ serverPlayer, items, message
+ );
+
+ serverPlayer.containerMenu = containerCartographyTable;
+
+ int id = containerCartographyTable.containerId;
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(id, MenuType.CARTOGRAPHY_TABLE, message));
+
+ sendItems(player, items);
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) {
+ NonNullList nmsItems = CustomInventoryUtil.convertToNMSItems(items);
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack cursor = CraftItemStack.asNMSCopy(player.getItemOnCursor());
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, cursor));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Gets the container id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the container id for
+ * @return the container id
+ * @since 0.10.14
+ */
+ @Contract(pure = true)
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container cartography table
+ *
+ * @since 0.10.14
+ */
+ private class ContainerCartographyTableImpl extends CartographyTableMenu {
+
+ /**
+ * The player for this cartography table container
+ */
+ @NotNull
+ private final Player player;
+
+ /**
+ * The internal bukkit entity for this container cartography table
+ */
+ @Nullable
+ private CraftInventoryView bukkitEntity;
+
+ /**
+ * Field for accessing the result inventory field
+ */
+ @NotNull
+ private final Field resultContainerField;
+
+ public ContainerCartographyTableImpl(@NotNull ServerPlayer serverPlayer,
+ @Nullable org.bukkit.inventory.ItemStack[] items,
+ @NotNull Component title) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory());
+
+ this.player = serverPlayer.getBukkitEntity();
+
+ setTitle(title);
+
+ try {
+ //noinspection JavaReflectionMemberAccess
+ this.resultContainerField = CartographyTableMenu.class.getDeclaredField("u"); //resultContainer
+ this.resultContainerField.setAccessible(true);
+ } catch (NoSuchFieldException exception) {
+ throw new RuntimeException(exception);
+ }
+
+ container.setItem(0, CraftItemStack.asNMSCopy(items[0]));
+ container.setItem(1, CraftItemStack.asNMSCopy(items[1]));
+
+ getResultInventory().setItem(0, CraftItemStack.asNMSCopy(items[2]));
+ }
+
+ @NotNull
+ @Override
+ public CraftInventoryView getBukkitView() {
+ if (bukkitEntity == null) {
+ CraftInventory inventory = new CraftInventoryCartography(super.container, getResultInventory()) {
+ @NotNull
+ @Contract(pure = true)
+ @Override
+ public InventoryHolder getHolder() {
+ return inventoryHolder;
+ }
+ };
+
+ bukkitEntity = new CraftInventoryView(player, inventory, this);
+ }
+
+ return bukkitEntity;
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ @NotNull
+ @Contract(pure = true)
+ private Container getResultInventory() {
+ try {
+ return (Container) resultContainerField.get(this);
+ } catch (IllegalAccessException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/EnchantingTableInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/EnchantingTableInventoryImpl.java
new file mode 100644
index 00000000..d3a65c70
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/EnchantingTableInventoryImpl.java
@@ -0,0 +1,218 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.EnchantmentMenu;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryEnchanting;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+/**
+ * Internal enchanting table inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class EnchantingTableInventoryImpl extends EnchantingTableInventory {
+
+ public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public void openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 2) {
+ throw new IllegalArgumentException(
+ "The amount of items for an enchanting table should be 2, but is '" + itemAmount + "'"
+ );
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ Component message = TextHolderUtil.toComponent(title);
+ ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(
+ serverPlayer, items, message
+ );
+
+ serverPlayer.containerMenu = containerEnchantmentTable;
+
+ int id = containerEnchantmentTable.containerId;
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(id, MenuType.ENCHANTMENT, message));
+
+ sendItems(player, items);
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) {
+ NonNullList nmsItems = NonNullList.of(
+ ItemStack.EMPTY,
+ CraftItemStack.asNMSCopy(items[0]),
+ CraftItemStack.asNMSCopy(items[1])
+ );
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack cursor = CraftItemStack.asNMSCopy(player.getItemOnCursor());
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, cursor));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Gets the containerId id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the containerId id for
+ * @return the containerId id
+ * @since 0.10.14
+ */
+ @Contract(pure = true)
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container enchanting table
+ *
+ * @since 0.10.14
+ */
+ private class ContainerEnchantingTableImpl extends EnchantmentMenu {
+
+ /**
+ * The player for this enchanting table container
+ */
+ @NotNull
+ private final Player player;
+
+ /**
+ * The internal bukkit entity for this container enchanting table
+ */
+ @Nullable
+ private CraftInventoryView bukkitEntity;
+
+ /**
+ * Field for accessing the enchant slots field
+ */
+ @NotNull
+ private final Field enchantSlotsField;
+
+ public ContainerEnchantingTableImpl(@NotNull ServerPlayer serverPlayer,
+ @Nullable org.bukkit.inventory.ItemStack[] items,
+ @NotNull Component title) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory());
+
+ this.player = serverPlayer.getBukkitEntity();
+
+ setTitle(title);
+
+ try {
+ //noinspection JavaReflectionMemberAccess
+ this.enchantSlotsField = EnchantmentMenu.class.getDeclaredField("o"); //enchantSlots
+ this.enchantSlotsField.setAccessible(true);
+ } catch (NoSuchFieldException exception) {
+ throw new RuntimeException(exception);
+ }
+
+ try {
+ Container input = (Container) enchantSlotsField.get(this);
+
+ input.setItem(0, CraftItemStack.asNMSCopy(items[0]));
+ input.setItem(1, CraftItemStack.asNMSCopy(items[1]));
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @NotNull
+ @Override
+ public CraftInventoryView getBukkitView() {
+ if (bukkitEntity == null) {
+ try {
+ CraftInventory inventory = new CraftInventoryEnchanting((Container) enchantSlotsField.get(this)) {
+ @NotNull
+ @Contract(pure = true)
+ @Override
+ public InventoryHolder getHolder() {
+ return inventoryHolder;
+ }
+ };
+
+ bukkitEntity = new CraftInventoryView(player, inventory, this);
+ } catch (IllegalAccessException exception) {
+ exception.printStackTrace();
+ }
+ }
+
+ return bukkitEntity;
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/GrindstoneInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/GrindstoneInventoryImpl.java
new file mode 100644
index 00000000..04f79f89
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/GrindstoneInventoryImpl.java
@@ -0,0 +1,208 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.CustomInventoryUtil;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.GrindstoneMenu;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.inventory.Slot;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Internal grindstone inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class GrindstoneInventoryImpl extends GrindstoneInventory {
+
+ public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public Inventory openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 3) {
+ throw new IllegalArgumentException(
+ "The amount of items for a grindstone should be 3, but is '" + itemAmount + "'"
+ );
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+
+ //ignore deprecation: superseding method is only available on Paper
+ //noinspection deprecation
+ CraftEventFactory.handleInventoryCloseEvent(serverPlayer);
+
+ serverPlayer.containerMenu = serverPlayer.inventoryMenu;
+
+ Component message = TextHolderUtil.toComponent(title);
+ ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(serverPlayer, message);
+
+ Inventory inventory = containerGrindstone.getBukkitView().getTopInventory();
+
+ inventory.setItem(0, items[0]);
+ inventory.setItem(1, items[1]);
+ inventory.setItem(2, items[2]);
+
+ int containerId = containerGrindstone.getContainerId();
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.GRINDSTONE, message));
+ serverPlayer.containerMenu = containerGrindstone;
+ serverPlayer.initMenu(containerGrindstone);
+
+ return inventory;
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items,
+ @Nullable org.bukkit.inventory.ItemStack cursor) {
+ if (cursor == null) {
+ throw new IllegalArgumentException("Cursor may not be null on version 1.19.2");
+ }
+
+ NonNullList nmsItems = CustomInventoryUtil.convertToNMSItems(items);
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack nmsCursor = CraftItemStack.asNMSCopy(cursor);
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, nmsCursor));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Gets the containerId id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the containerId id for
+ * @return the containerId id
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Contract(pure = true)
+ @Deprecated
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @NotNull
+ @Contract(pure = true)
+ @Deprecated
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container grindstone
+ *
+ * @since 0.10.14
+ */
+ private static class ContainerGrindstoneImpl extends GrindstoneMenu {
+
+ /**
+ * Creates a new grindstone container
+ *
+ * @param serverPlayer the player for whom this container should be opened
+ * @param title the title of the gui
+ * @since 0.10.14
+ */
+ public ContainerGrindstoneImpl(@NotNull ServerPlayer serverPlayer, @NotNull Component title) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory());
+
+ setTitle(title);
+
+ Slot firstSlot = this.slots.get(0);
+ Slot secondSlot = this.slots.get(1);
+ Slot thirdSlot = this.slots.get(2);
+
+ this.slots.set(0, new Slot(firstSlot.container, firstSlot.index, firstSlot.x, firstSlot.y) {
+ @Override
+ public boolean mayPlace(ItemStack stack) {
+ return true;
+ }
+ });
+
+ this.slots.set(1, new Slot(secondSlot.container, secondSlot.index, secondSlot.x, secondSlot.y) {
+ @Override
+ public boolean mayPlace(ItemStack stack) {
+ return true;
+ }
+ });
+
+ this.slots.set(2, new Slot(thirdSlot.container, thirdSlot.index, thirdSlot.x, thirdSlot.y) {
+ @Override
+ public boolean mayPlace(ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {}
+ });
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ public int getContainerId() {
+ return this.containerId;
+ }
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/MerchantInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/MerchantInventoryImpl.java
new file mode 100644
index 00000000..4ec8a905
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/MerchantInventoryImpl.java
@@ -0,0 +1,118 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory;
+import net.minecraft.core.component.DataComponentPredicate;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.item.trading.ItemCost;
+import net.minecraft.world.item.trading.MerchantOffer;
+import net.minecraft.world.item.trading.MerchantOffers;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.MerchantRecipe;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Internal merchant inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class MerchantInventoryImpl extends MerchantInventory {
+
+ @Override
+ public void sendMerchantOffers(@NotNull Player player,
+ @NotNull List extends Map.Entry extends MerchantRecipe, ? extends Integer>> trades,
+ int level, int experience) {
+ MerchantOffers offers = new MerchantOffers();
+
+ for (Map.Entry extends MerchantRecipe, ? extends Integer> entry : trades) {
+ MerchantRecipe recipe = entry.getKey();
+ List ingredients = recipe.getIngredients();
+
+ if (ingredients.size() < 1) {
+ throw new IllegalStateException("Merchant recipe has no ingredients");
+ }
+
+ ItemStack itemA = ingredients.get(0);
+ ItemStack itemB = null;
+
+ if (ingredients.size() >= 2) {
+ itemB = ingredients.get(1);
+ }
+
+ net.minecraft.world.item.ItemStack nmsItemA = CraftItemStack.asNMSCopy(itemA);
+ net.minecraft.world.item.ItemStack nmsItemB = net.minecraft.world.item.ItemStack.EMPTY;
+ net.minecraft.world.item.ItemStack nmsItemResult = CraftItemStack.asNMSCopy(recipe.getResult());
+
+ if (itemB != null) {
+ nmsItemB = CraftItemStack.asNMSCopy(itemB);
+ }
+
+ ItemCost itemCostA = convertItemStackToItemCost(nmsItemA);
+ ItemCost itemCostB = convertItemStackToItemCost(nmsItemB);
+
+ int uses = recipe.getUses();
+ int maxUses = recipe.getMaxUses();
+ int exp = recipe.getVillagerExperience();
+ float multiplier = recipe.getPriceMultiplier();
+
+ MerchantOffer merchantOffer = new MerchantOffer(
+ itemCostA, Optional.of(itemCostB), nmsItemResult, uses, maxUses, exp, multiplier
+ );
+ merchantOffer.setSpecialPriceDiff(entry.getValue());
+
+ offers.add(merchantOffer);
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+
+ serverPlayer.sendMerchantOffers(containerId, offers, level, experience, true, false);
+ }
+
+ /**
+ * Converts an NMS item stack to an item cost.
+ *
+ * @param itemStack the item stack to convert
+ * @return the item cost
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(value = "_ -> new", pure = true)
+ private ItemCost convertItemStackToItemCost(@NotNull net.minecraft.world.item.ItemStack itemStack) {
+ DataComponentPredicate predicate = DataComponentPredicate.allOf(itemStack.getComponents());
+
+ return new ItemCost(itemStack.getItemHolder(), itemStack.getCount(), predicate, itemStack);
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * Gets the containerId id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the containerId id for
+ * @return the containerId id
+ * @since 0.10.14
+ */
+ @Contract(pure = true)
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/SmithingTableInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/SmithingTableInventoryImpl.java
new file mode 100644
index 00000000..145f05e9
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/SmithingTableInventoryImpl.java
@@ -0,0 +1,335 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.CustomInventoryUtil;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.ContainerLevelAccess;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.inventory.Slot;
+import net.minecraft.world.inventory.SmithingMenu;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Internal smithing table inventory for 1.20.6. This is only available for Minecraft 1.20 and higher.
+ *
+ * @since 0.10.14
+ */
+public class SmithingTableInventoryImpl extends SmithingTableInventory {
+
+ public SmithingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @NotNull
+ @Override
+ public Inventory openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 4) {
+ throw new IllegalArgumentException(
+ "The amount of items for a smithing table should be 4, but is '" + itemAmount + "'"
+ );
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+
+ //ignore deprecation: superseding method is only available on Paper
+ //noinspection deprecation
+ CraftEventFactory.handleInventoryCloseEvent(serverPlayer);
+
+ serverPlayer.containerMenu = serverPlayer.inventoryMenu;
+
+ Component message = TextHolderUtil.toComponent(title);
+ ContainerSmithingTableImpl containerSmithingTable = new ContainerSmithingTableImpl(serverPlayer, message);
+
+ Inventory inventory = containerSmithingTable.getBukkitView().getTopInventory();
+
+ inventory.setItem(0, items[0]);
+ inventory.setItem(1, items[1]);
+ inventory.setItem(2, items[2]);
+ inventory.setItem(3, items[3]);
+
+ int containerId = containerSmithingTable.getContainerId();
+
+ serverPlayer.connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.SMITHING, message));
+ serverPlayer.containerMenu = containerSmithingTable;
+ serverPlayer.initMenu(containerSmithingTable);
+
+ return inventory;
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items,
+ @Nullable org.bukkit.inventory.ItemStack cursor) {
+ NonNullList nmsItems = CustomInventoryUtil.convertToNMSItems(items);
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack nmsCursor = CraftItemStack.asNMSCopy(cursor);
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, nmsCursor));
+ }
+
+ @Override
+ public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 0, nmsItem));
+ }
+
+ @Override
+ public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 1, nmsItem));
+ }
+
+ @Override
+ public void sendResultItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) {
+ sendResultItem(player, CraftItemStack.asNMSCopy(item));
+ }
+
+ @Override
+ public void clearResultItem(@NotNull Player player) {
+ sendResultItem(player, ItemStack.EMPTY);
+ }
+
+ @Override
+ public void setCursor(@NotNull Player player, @NotNull org.bukkit.inventory.ItemStack item) {
+ setCursor(player, CraftItemStack.asNMSCopy(item));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Sets the cursor of the given player
+ *
+ * @param player the player to set the cursor
+ * @param item the item to set the cursor to
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Deprecated
+ private void setCursor(@NotNull Player player, @NotNull ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, item));
+ }
+
+ /**
+ * Sends the result item to the specified player with the given item
+ *
+ * @param player the player to send the result item to
+ * @param item the result item
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Deprecated
+ private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(containerId, state, 2, item));
+ }
+
+ /**
+ * Gets the container id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the container id for
+ * @return the container id
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @Contract(pure = true)
+ @Deprecated
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ * @deprecated no longer used internally
+ */
+ @NotNull
+ @Contract(pure = true)
+ @Deprecated
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container smithing table
+ *
+ * @since 0.10.14
+ */
+ private static class ContainerSmithingTableImpl extends SmithingMenu {
+
+ /**
+ * Creates a new custom smithing table container for the specified player
+ *
+ * @param serverPlayer the player for whom this anvil container is
+ * @param title the title of the inventory
+ * @since 0.10.14
+ */
+ public ContainerSmithingTableImpl(@NotNull ServerPlayer serverPlayer, @NotNull Component title) {
+ super(serverPlayer.nextContainerCounter(), serverPlayer.getInventory(),
+ ContainerLevelAccess.create(serverPlayer.getCommandSenderWorld(), new BlockPos(0, 0, 0)));
+
+ setTitle(title);
+
+ this.checkReachable = false;
+
+ Slot slotOne = this.slots.get(0);
+ Slot slotTwo = this.slots.get(1);
+ Slot slotThree = this.slots.get(2);
+ Slot slotFour = this.slots.get(3);
+
+ this.slots.set(0, new Slot(slotOne.container, slotOne.index, slotOne.x, slotOne.y) {
+ @Override
+ public boolean mayPlace(@NotNull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public boolean mayPickup(net.minecraft.world.entity.player.@NotNull Player playerEntity) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {
+ slotOne.onTake(player, stack);
+ }
+ });
+
+ this.slots.set(1, new Slot(slotTwo.container, slotTwo.index, slotTwo.x, slotTwo.y) {
+ @Override
+ public boolean mayPlace(@NotNull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public boolean mayPickup(net.minecraft.world.entity.player.@NotNull Player playerEntity) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {
+ slotTwo.onTake(player, stack);
+ }
+ });
+
+ this.slots.set(2, new Slot(slotThree.container, slotThree.index, slotThree.x, slotThree.y) {
+ @Override
+ public boolean mayPlace(@NotNull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public boolean mayPickup(net.minecraft.world.entity.player.@NotNull Player playerEntity) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {
+ slotThree.onTake(player, stack);
+ }
+ });
+
+ this.slots.set(3, new Slot(slotFour.container, slotFour.index, slotFour.x, slotFour.y) {
+ @Override
+ public boolean mayPlace(@NotNull ItemStack stack) {
+ return true;
+ }
+
+ @Override
+ public boolean mayPickup(net.minecraft.world.entity.player.@NotNull Player playerEntity) {
+ return true;
+ }
+
+ @Override
+ public void onTake(net.minecraft.world.entity.player.@NotNull Player player, @NotNull ItemStack stack) {
+ slotFour.onTake(player, stack);
+ }
+ });
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ @Override
+ public void createResult() {}
+
+ @Override
+ protected void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {}
+
+ @Override
+ protected boolean mayPickup(net.minecraft.world.entity.player.Player player, boolean present) {
+ return true;
+ }
+
+ public int getContainerId() {
+ return this.containerId;
+ }
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/StonecutterInventoryImpl.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/StonecutterInventoryImpl.java
new file mode 100644
index 00000000..62b21ba9
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/StonecutterInventoryImpl.java
@@ -0,0 +1,221 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6;
+
+import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.Container;
+import net.minecraft.world.inventory.MenuType;
+import net.minecraft.world.inventory.StonecutterMenu;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventory;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryStonecutter;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.InventoryHolder;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+/**
+ * Internal stonecutter inventory for 1.20.6
+ *
+ * @since 0.10.14
+ */
+public class StonecutterInventoryImpl extends StonecutterInventory {
+
+ public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) {
+ super(inventoryHolder);
+ }
+
+ @Override
+ public void openInventory(@NotNull Player player, @NotNull TextHolder title,
+ @Nullable org.bukkit.inventory.ItemStack[] items) {
+ int itemAmount = items.length;
+
+ if (itemAmount != 2) {
+ throw new IllegalArgumentException(
+ "The amount of items for a stonecutter should be 2, but is '" + itemAmount + "'"
+ );
+ }
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ Component message = TextHolderUtil.toComponent(title);
+ ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(serverPlayer, items, message);
+
+ serverPlayer.containerMenu = containerEnchantmentTable;
+
+ int id = containerEnchantmentTable.containerId;
+ ClientboundOpenScreenPacket packet = new ClientboundOpenScreenPacket(id, MenuType.STONECUTTER, message);
+
+ serverPlayer.connection.send(packet);
+
+ sendItems(player, items);
+ }
+
+ @Override
+ public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) {
+ NonNullList nmsItems = NonNullList.of(
+ ItemStack.EMPTY,
+ CraftItemStack.asNMSCopy(items[0]),
+ CraftItemStack.asNMSCopy(items[1])
+ );
+
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int containerId = getContainerId(serverPlayer);
+ int state = serverPlayer.containerMenu.incrementStateId();
+ ItemStack cursor = CraftItemStack.asNMSCopy(player.getItemOnCursor());
+ ServerPlayerConnection playerConnection = getPlayerConnection(serverPlayer);
+
+ playerConnection.send(new ClientboundContainerSetContentPacket(containerId, state, nmsItems, cursor));
+ }
+
+ @Override
+ public void clearCursor(@NotNull Player player) {
+ ServerPlayer serverPlayer = getServerPlayer(player);
+ int state = serverPlayer.containerMenu.incrementStateId();
+
+ getPlayerConnection(serverPlayer).send(new ClientboundContainerSetSlotPacket(-1, state, -1, ItemStack.EMPTY));
+ }
+
+ /**
+ * Gets the container id for the inventory view the player currently has open
+ *
+ * @param nmsPlayer the player to get the container id for
+ * @return the container id
+ * @since 0.10.14
+ */
+ @Contract(pure = true)
+ private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {
+ return nmsPlayer.containerMenu.containerId;
+ }
+
+ /**
+ * Gets the player connection for the specified player
+ *
+ * @param serverPlayer the player to get the player connection from
+ * @return the player connection
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayerConnection getPlayerConnection(@NotNull ServerPlayer serverPlayer) {
+ return serverPlayer.connection;
+ }
+
+ /**
+ * Gets the server player associated to this player
+ *
+ * @param player the player to get the server player from
+ * @return the server player
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private ServerPlayer getServerPlayer(@NotNull Player player) {
+ return ((CraftPlayer) player).getHandle();
+ }
+
+ /**
+ * A custom container enchanting table
+ *
+ * @since 0.10.14
+ */
+ private class ContainerStonecutterImpl extends StonecutterMenu {
+
+ /**
+ * The player for this enchanting table container
+ */
+ @NotNull
+ private final Player player;
+
+ /**
+ * The internal bukkit entity for this container enchanting table
+ */
+ @Nullable
+ private CraftInventoryView bukkitEntity;
+
+ /**
+ * Field for accessing the result inventory field
+ */
+ @NotNull
+ private final Field resultContainerField;
+
+ public ContainerStonecutterImpl(@NotNull ServerPlayer entityPlayer,
+ @Nullable org.bukkit.inventory.ItemStack[] items, @NotNull Component title) {
+ super(entityPlayer.nextContainerCounter(), entityPlayer.getInventory());
+
+ this.player = entityPlayer.getBukkitEntity();
+
+ setTitle(title);
+
+ try {
+ //noinspection JavaReflectionMemberAccess
+ this.resultContainerField = StonecutterMenu.class.getDeclaredField("A"); //resultContainer
+ this.resultContainerField.setAccessible(true);
+ } catch (NoSuchFieldException exception) {
+ throw new RuntimeException(exception);
+ }
+
+ container.setItem(0, CraftItemStack.asNMSCopy(items[0]));
+ getResultInventory().setItem(0, CraftItemStack.asNMSCopy(items[1]));
+ }
+
+ @NotNull
+ @Override
+ public CraftInventoryView getBukkitView() {
+ if (bukkitEntity == null) {
+ CraftInventory inventory = new CraftInventoryStonecutter(this.container, getResultInventory()) {
+ @NotNull
+ @Contract(pure = true)
+ @Override
+ public InventoryHolder getHolder() {
+ return inventoryHolder;
+ }
+ };
+
+ bukkitEntity = new CraftInventoryView(player, inventory, this);
+ }
+
+ return bukkitEntity;
+ }
+
+ @Contract(pure = true, value = "_ -> true")
+ @Override
+ public boolean stillValid(@Nullable net.minecraft.world.entity.player.Player nmsPlayer) {
+ return true;
+ }
+
+ @Override
+ public void slotsChanged(Container container) {}
+
+ @Override
+ public void removed(net.minecraft.world.entity.player.Player nmsPlayer) {}
+
+ /**
+ * Gets the result inventory
+ *
+ * @return the result inventory
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ public Container getResultInventory() {
+ try {
+ return (Container) resultContainerField.get(this);
+ } catch (IllegalAccessException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/CustomInventoryUtil.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/CustomInventoryUtil.java
new file mode 100644
index 00000000..a0f724f1
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/CustomInventoryUtil.java
@@ -0,0 +1,41 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6.util;
+
+import net.minecraft.core.NonNullList;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A utility class for custom inventories
+ *
+ * @since 0.10.14
+ */
+public final class CustomInventoryUtil {
+
+ /**
+ * A private constructor to prevent construction.
+ */
+ private CustomInventoryUtil() {}
+
+ /**
+ * Converts an array of Bukkit items into a non-null list of NMS items. The returned list is modifiable. If no items
+ * were specified, this returns an empty list.
+ *
+ * @param items the items to convert
+ * @return a list of converted items
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ public static NonNullList convertToNMSItems(@Nullable org.bukkit.inventory.ItemStack @NotNull [] items) {
+ NonNullList nmsItems = NonNullList.create();
+
+ for (org.bukkit.inventory.ItemStack item : items) {
+ nmsItems.add(CraftItemStack.asNMSCopy(item));
+ }
+
+ return nmsItems;
+ }
+}
diff --git a/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/TextHolderUtil.java b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/TextHolderUtil.java
new file mode 100644
index 00000000..8e945b8b
--- /dev/null
+++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/util/TextHolderUtil.java
@@ -0,0 +1,67 @@
+package com.github.stefvanschie.inventoryframework.nms.v1_20_6.util;
+
+import com.github.stefvanschie.inventoryframework.adventuresupport.ComponentHolder;
+import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder;
+import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder;
+import net.minecraft.core.HolderLookup;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A utility class for adding {@link TextHolder} support.
+ *
+ * @since 0.10.14
+ */
+public final class TextHolderUtil {
+
+ private TextHolderUtil() {
+ //private constructor to prevent construction
+ }
+
+ /**
+ * Converts the specified value to a vanilla component.
+ *
+ * @param holder the value to convert
+ * @return the value as a vanilla component
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ public static Component toComponent(@NotNull TextHolder holder) {
+ if (holder instanceof StringHolder) {
+ return toComponent((StringHolder) holder);
+ } else {
+ return toComponent((ComponentHolder) holder);
+ }
+ }
+
+ /**
+ * Converts the specified legacy string holder to a vanilla component.
+ *
+ * @param holder the value to convert
+ * @return the value as a vanilla component
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private static Component toComponent(@NotNull StringHolder holder) {
+ return Component.literal(holder.asLegacyString());
+ }
+
+ /**
+ * Converts the specified Adventure component holder to a vanilla component.
+ *
+ * @param holder the value to convert
+ * @return the value as a vanilla component
+ * @since 0.10.14
+ */
+ @NotNull
+ @Contract(pure = true)
+ private static Component toComponent(@NotNull ComponentHolder holder) {
+ return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson(), HolderLookup.Provider.create(Stream.empty())));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6bd6e3ec..673bef24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,6 +7,7 @@
IF
nms/abstraction
+ nms/1_20_6
nms/1_20_5
nms/1_20_3-4
nms/1_20_2