diff --git a/.editorconfig b/.editorconfig index 21392b2d0..89050be96 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,4 +2,14 @@ root = true # Indentation override for all java files [**.java] +indent_style = space indent_size = 4 +insert_final_newline = true +ij_continuation_indent_size = 8 + +# Indentation override for all +[pom.xml] +indent_style = space +indent_size = 4 +insert_final_newline = false +ij_continuation_indent_size = 8 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..daec31893 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/deployment/deploy.sh b/.github/deployment/deploy.sh new file mode 100644 index 000000000..ccf6f861c --- /dev/null +++ b/.github/deployment/deploy.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +PARENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -pl :IF-parent -q -DforceStdout) +BASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -pl :IF -q -DforceStdout) + +if [[ $PARENT_VERSION != "$BASE_VERSION" ]]; then + echo "IF-parent and IF versions mismatch" + exit 1 +fi + +if [[ $PARENT_VERSION != *-SNAPSHOT ]]; then + exit 0 +fi + +if ! mvn clean install -B; then + echo "Unable to build IF" + exit 1 +fi + +if ! mvn deploy -N -pl :IF-parent -P deploy -s ./.github/deployment/settings.xml -B -Dgpg.passphrase="$1" -Ddeploy.username="$2" -Ddeploy.password="$3"; then + echo "Unable to deploy IF-parent" + exit 1 +fi + +if ! mvn deploy -pl :IF -P deploy -s ./.github/deployment/settings.xml -B -Dgpg.passphrase="$1" -Ddeploy.username="$2" -Ddeploy.password="$3"; then + echo "Unable to deploy IF" +fi diff --git a/.github/deployment/settings.xml b/.github/deployment/settings.xml new file mode 100644 index 000000000..f27e56407 --- /dev/null +++ b/.github/deployment/settings.xml @@ -0,0 +1,9 @@ + + + + central + ${deploy.username} + ${deploy.password} + + + \ No newline at end of file diff --git a/.github/workflows/auto-deploy.yml b/.github/workflows/auto-deploy.yml new file mode 100644 index 000000000..99dc1eb37 --- /dev/null +++ b/.github/workflows/auto-deploy.yml @@ -0,0 +1,123 @@ +name: Automatically deploy the project + +on: + push: + branches: + - master + +jobs: + deploy: + runs-on: ubuntu-latest + name: 'Deploy' + steps: + - name: Cache Paper(clip) jars + id: cache-paperclip-jars + uses: actions/cache@v4 + with: + path: | + .paper-nms + build + paperclip + ~/.m2/repository/io/papermc/paper + ~/.m2/repository/ca/bkaw/paper-nms + ~/.m2/repository/org/spigotmc + key: ${{ runner.os }}-${{ secrets.CACHE_VERSION }}-paperclip + - name: Download Paperclip jars + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mkdir -p paperclip + wget https://api.papermc.io/v2/projects/paper/versions/1.16.5/builds/794/downloads/paper-1.16.5-794.jar -O paperclip/paper-1.16.5.jar + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Generate 1.16 Paper jar + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + working-directory: paperclip + run: | + java -jar paper-1.16.5.jar + - name: Install 1.16 Paper jar + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + working-directory: paperclip + run: | + mvn install:install-file -Dfile=cache/patched_1.16.5.jar -DgroupId="io.papermc" -DartifactId="paper" -Dversion="1.16.5-R0.1-SNAPSHOT" -Dpackaging="jar" -DgeneratePom="true" + - name: Set up JDK 21 + uses: actions/setup-java@v1 + with: + java-version: 21 + - uses: actions/checkout@v2 + name: Checkout code + - name: Run paper-nms init + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mvn paper-nms:init -pl nms/1_17_1 + mvn paper-nms:init -pl nms/1_18_2 + mvn paper-nms:init -pl nms/1_19_4 + mvn paper-nms:init -pl nms/1_20_0 + 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-1.20.6, 1.21' + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mkdir -p build + cd build/ + wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O BuildTools.jar + + git clone https://hub.spigotmc.org/stash/scm/spigot/bukkit.git Bukkit + cd Bukkit + git checkout 304e83eb384c338546aa96eea51388e0e8407e26 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/craftbukkit.git CraftBukkit + cd CraftBukkit + git checkout 91b1fc3f1cf89e2591367dca1fa7362fe376f289 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/spigot.git Spigot + cd Spigot + git checkout b698b49caf14f97a717afd67e13fd7ac59f51089 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/builddata.git BuildData + cd BuildData + git checkout a7f7c2118b877fde4cf0f32f1f730ffcdee8e9ee + cd .. + + java -jar BuildTools.jar --remapped --disable-java-check --dont-update + java -jar BuildTools.jar --rev 1.20.6 --remapped --disable-java-check + + cd Bukkit + git checkout 2ec53f498e32b3af989cb24672fc54dfab087154 + cd .. + + cd CraftBukkit + git checkout 8ee6fd1b8db9896590aa321d0199453de1fc35db + cd .. + + cd Spigot + git checkout fb8fb722a327a2f9f097f2ded700ac5de8157408 + cd .. + + cd BuildData + git checkout ae1e7b1e31cd3a3892bb05a6ccdcecc48c73c455 + cd .. + + java -jar BuildTools.jar --remapped --disable-java-check --dont-update + java -jar BuildTools.jar --rev 1.21.1 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.3 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.4 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.5 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.8 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.10 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.11 --remapped --disable-java-check + + cd ../ + - name: Setup GPG keys + run: cat <(echo -e "${{ secrets.GPG_KEY }}") | gpg --batch --import + - name: Automatically deploy the project + env: + gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} + deploy_username: ${{ secrets.DEPLOY_USERNAME }} + deploy_password: ${{ secrets.DEPLOY_PASSWORD }} + run: bash ./.github/deployment/deploy.sh "$gpg_passphrase" "$deploy_username" "$deploy_password" diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 9df069e74..e41d6c59a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -16,17 +16,108 @@ jobs: runs-on: ubuntu-latest name: 'Build' steps: - - run: | - wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O BuildTools.jar - java -jar BuildTools.jar --rev 1.14.4 - java -jar BuildTools.jar --rev 1.15.2 - java -jar BuildTools.jar --rev 1.16.1 - java -jar BuildTools.jar --rev 1.16.3 - java -jar BuildTools.jar --rev 1.16.4 - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 + - name: Cache Paper(clip) jars + id: cache-paperclip-jars + uses: actions/cache@v4 + with: + path: | + .paper-nms + build + paperclip + ~/.m2/repository/io/papermc/paper + ~/.m2/repository/ca/bkaw/paper-nms + ~/.m2/repository/org/spigotmc + key: ${{ runner.os }}-${{ secrets.CACHE_VERSION }}-paperclip + - name: Download Paperclip jar + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mkdir -p paperclip + wget https://api.papermc.io/v2/projects/paper/versions/1.16.5/builds/794/downloads/paper-1.16.5-794.jar -O paperclip/paper-1.16.5.jar + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 + - name: Generate 1.16 Paper jar + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + working-directory: paperclip + run: | + java -jar paper-1.16.5.jar + - name: Install 1.16 Paper jar + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + working-directory: paperclip + run: | + mvn install:install-file -Dfile=cache/patched_1.16.5.jar -DgroupId="io.papermc" -DartifactId="paper" -Dversion="1.16.5-R0.1-SNAPSHOT" -Dpackaging="jar" -DgeneratePom="true" + - name: Set up JDK 21 + uses: actions/setup-java@v1 + with: + java-version: 21 + - uses: actions/checkout@v2 + name: Checkout code + - name: Run paper-nms init + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mvn paper-nms:init -pl nms/1_17_1 + mvn paper-nms:init -pl nms/1_18_2 + mvn paper-nms:init -pl nms/1_19_4 + mvn paper-nms:init -pl nms/1_20_0 + 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-1.20.6, 1.21' + if: steps.cache-paperclip-jars.outputs.cache-hit != 'true' + run: | + mkdir -p build + cd build/ + wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O BuildTools.jar + + git clone https://hub.spigotmc.org/stash/scm/spigot/bukkit.git Bukkit + cd Bukkit + git checkout 304e83eb384c338546aa96eea51388e0e8407e26 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/craftbukkit.git CraftBukkit + cd CraftBukkit + git checkout 91b1fc3f1cf89e2591367dca1fa7362fe376f289 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/spigot.git Spigot + cd Spigot + git checkout b698b49caf14f97a717afd67e13fd7ac59f51089 + cd .. + + git clone https://hub.spigotmc.org/stash/scm/spigot/builddata.git BuildData + cd BuildData + git checkout a7f7c2118b877fde4cf0f32f1f730ffcdee8e9ee + cd .. + + java -jar BuildTools.jar --remapped --disable-java-check --dont-update + java -jar BuildTools.jar --rev 1.20.6 --remapped --disable-java-check + + cd Bukkit + git checkout 2ec53f498e32b3af989cb24672fc54dfab087154 + cd .. + + cd CraftBukkit + git checkout 8ee6fd1b8db9896590aa321d0199453de1fc35db + cd .. + + cd Spigot + git checkout fb8fb722a327a2f9f097f2ded700ac5de8157408 + cd .. + + cd BuildData + git checkout ae1e7b1e31cd3a3892bb05a6ccdcecc48c73c455 + cd .. + + java -jar BuildTools.jar --remapped --disable-java-check --dont-update + java -jar BuildTools.jar --rev 1.21.1 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.3 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.4 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.5 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.8 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.10 --remapped --disable-java-check + java -jar BuildTools.jar --rev 1.21.11 --remapped --disable-java-check + + cd ../ - name: Build with Maven run: mvn -B package --file pom.xml diff --git a/.gitignore b/.gitignore index 77a4b1128..c78733736 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ target IF-Test *.iml *.txt -dependency-reduced-pom.xml \ No newline at end of file +dependency-reduced-pom.xml +*.log +.paper-nms \ No newline at end of file diff --git a/IF/pom.xml b/IF/pom.xml index 90d71667b..2d63c5692 100644 --- a/IF/pom.xml +++ b/IF/pom.xml @@ -5,13 +5,16 @@ IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + 0.12.0-SNAPSHOT + ../pom.xml 4.0.0 IF jar + IF + false @@ -27,7 +30,25 @@ + + + + org.junit + junit-bom + 6.0.1 + pom + import + + + + + + net.kyori + adventure-api + ${adventure.version} + provided + com.github.stefvanschie.inventoryframework abstraction @@ -36,63 +57,178 @@ com.github.stefvanschie.inventoryframework - 1_14_R1 + 1_16_5 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_17_1 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_18_2 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_19_4 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_0 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_1 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_2 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_3-4 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_5 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_20_6 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_21_0 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_21_1 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_21_2-3 ${project.version} compile com.github.stefvanschie.inventoryframework - 1_15_R1 + 1_21_4 ${project.version} compile com.github.stefvanschie.inventoryframework - 1_16_R1 + 1_21_5 ${project.version} compile com.github.stefvanschie.inventoryframework - 1_16_R2 + 1_21_6-8 ${project.version} compile com.github.stefvanschie.inventoryframework - 1_16_R3 + 1_21_9-10 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + 1_21_11 + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + iv-abstraction + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + iv-abstract-class + ${project.version} + compile + + + com.github.stefvanschie.inventoryframework + iv-interface ${project.version} compile org.spigotmc spigot-api - 1.16.4-R0.1-SNAPSHOT + 1.20.3-R0.1-20231207.085553-9 provided + + + + org.apache.commons + commons-lang3 + + com.mojang authlib - 1.5.21 + 1.5.26 provided + + + + org.apache.commons + commons-lang3 + + org.junit.jupiter - junit-jupiter-api - 5.3.1 + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-suite + test + + + org.junit.jupiter + junit-jupiter-params test - ossrh - https://oss.sonatype.org/content/repositories/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - @@ -103,7 +239,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.8 sign-artifacts @@ -126,24 +262,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.0 - - - org.junit.platform - junit-platform-surefire-provider - 1.2.0-M1 - - - org.junit.jupiter - junit-jupiter-engine - 5.2.0 - - + 3.5.4 org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.6.1 package @@ -154,20 +278,20 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published org.apache.maven.plugins maven-source-plugin - 3.2.0 + 3.4.0 attach-sources @@ -180,7 +304,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.12.0 attach-javadocs diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/HumanEntityCache.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/HumanEntityCache.java index 51d6920c7..cb0b11798 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/HumanEntityCache.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/HumanEntityCache.java @@ -155,6 +155,17 @@ private void restoreAll() { inventories.keySet().forEach(this::restore); } + /** + * Checks if the provided human entity appears in the cache, returning true if this is the case. + * + * @param humanEntity the human entity to check whether it is present in the cache + * @return true if the human entity is in the cache, false otherwise + * @since 0.10.7 + */ + public boolean contains(@NotNull HumanEntity humanEntity) { + return this.inventories.containsKey(humanEntity); + } + /** * Clear the cache for the specified human entity * diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiItem.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiItem.java index fe09b0d6a..3c63c1203 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiItem.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiItem.java @@ -1,5 +1,6 @@ package com.github.stefvanschie.inventoryframework.gui; +import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil; import com.github.stefvanschie.inventoryframework.util.UUIDTagType; import org.bukkit.NamespacedKey; import org.bukkit.event.inventory.InventoryClickEvent; @@ -7,6 +8,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -24,11 +26,18 @@ */ public class GuiItem { + /** + * The logger to log errors with + */ + @NotNull + private final Logger logger; + /** * The {@link NamespacedKey} that specifies the location of the (internal) {@link UUID} in {@link PersistentDataContainer}s. * The {@link PersistentDataType} that should be used is {@link UUIDTagType}. */ - public static final NamespacedKey KEY_UUID = new NamespacedKey(JavaPlugin.getProvidingPlugin(GuiItem.class), "IF-uuid"); + @NotNull + private final NamespacedKey keyUUID; /** * An action for the inventory @@ -46,7 +55,7 @@ public class GuiItem { * The items shown */ @NotNull - private final ItemStack item; + private ItemStack item; /** * Whether this item is visible or not @@ -64,15 +73,34 @@ public class GuiItem { * * @param item the item stack * @param action the action called whenever an interaction with this item happens + * @param plugin the owning plugin of this item + * @see #GuiItem(ItemStack, Consumer) + * @since 0.10.8 */ - public GuiItem(@NotNull ItemStack item, @Nullable Consumer action) { - this.action = action; - this.visible = true; - this.properties = new ArrayList<>(); - this.item = item; + public GuiItem(@NotNull ItemStack item, @Nullable Consumer action, @NotNull Plugin plugin) { + this(item, action, plugin.getLogger(), new NamespacedKey(plugin, "IF-uuid")); + } - //remove this call after the removal of InventoryComponent#setItem(ItemStack, int, int) - applyUUID(); + /** + * Creates a new gui item based on the item stack and action + * + * @param item the item stack + * @param plugin the owning plugin of this item + * @see #GuiItem(ItemStack) + * @since 0.10.8 + */ + public GuiItem(@NotNull ItemStack item, @NotNull Plugin plugin) { + this(item, event -> {}, plugin); + } + + /** + * Creates a new gui item based on the item stack and action + * + * @param item the item stack + * @param action the action called whenever an interaction with this item happens + */ + public GuiItem(@NotNull ItemStack item, @Nullable Consumer action) { + this(item, action, JavaPlugin.getProvidingPlugin(GuiItem.class)); } /** @@ -81,7 +109,30 @@ public GuiItem(@NotNull ItemStack item, @Nullable Consumer * @param item the item stack */ public GuiItem(@NotNull ItemStack item) { - this(item, null); + this(item, event -> {}); + } + + /** + * Creates a new gui item based on the given item, action, logger, and key. The logger will be used for logging + * exceptions and the key is used for identification of this item. + * + * @param item the item stack + * @param action the action called whenever an interaction with this item happens + * @param logger the logger used for logging exceptions + * @param key the key to identify this item with + * @since 0.10.10 + */ + private GuiItem(@NotNull ItemStack item, @Nullable Consumer action, @NotNull Logger logger, + @NotNull NamespacedKey key) { + this.logger = logger; + this.keyUUID = key; + this.action = action; + this.visible = true; + this.properties = new ArrayList<>(); + this.item = item; + + //remove this call after the removal of InventoryComponent#setItem(ItemStack, int, int) + applyUUID(); } /** @@ -95,20 +146,18 @@ public GuiItem(@NotNull ItemStack item) { @NotNull @Contract(pure = true) public GuiItem copy() { - GuiItem guiItem = new GuiItem(item.clone(), action); + GuiItem guiItem = new GuiItem(item.clone(), action, this.logger, this.keyUUID); guiItem.visible = visible; guiItem.uuid = uuid; guiItem.properties = new ArrayList<>(properties); ItemMeta meta = guiItem.item.getItemMeta(); - if (meta == null) { - throw new IllegalArgumentException("item must be able to have ItemMeta (it mustn't be AIR)"); + if (meta != null) { + meta.getPersistentDataContainer().set(keyUUID, UUIDTagType.INSTANCE, guiItem.uuid); + guiItem.item.setItemMeta(meta); } - meta.getPersistentDataContainer().set(KEY_UUID, UUIDTagType.INSTANCE, guiItem.uuid); - guiItem.item.setItemMeta(meta); - return guiItem; } @@ -128,9 +177,9 @@ public void callAction(@NotNull InventoryClickEvent event) { try { action.accept(event); } catch (Throwable t) { - Logger logger = JavaPlugin.getProvidingPlugin(getClass()).getLogger(); - logger.log(Level.SEVERE, "Exception while handling click event in inventory '" - + event.getView().getTitle() + "', slot=" + event.getSlot() + ", item=" + item.getType(), t); + this.logger.log(Level.SEVERE, "Exception while handling click event in inventory '" + + InventoryViewUtil.getInstance().getTitle(event.getView()) + "', slot=" + event.getSlot() + + ", item=" + item.getType(), t); } } @@ -144,11 +193,21 @@ public void applyUUID() { ItemMeta meta = item.getItemMeta(); if (meta != null) { - meta.getPersistentDataContainer().set(KEY_UUID, UUIDTagType.INSTANCE, uuid); + meta.getPersistentDataContainer().set(this.keyUUID, UUIDTagType.INSTANCE, uuid); item.setItemMeta(meta); } } + /** + * Overwrites the current item with the provided item. + * + * @param item the item to set + * @since 0.10.8 + */ + public void setItem(@NotNull ItemStack item) { + this.item = item; + } + /** * Sets the action to be executed when a human entity clicks on this item. * @@ -192,6 +251,18 @@ public ItemStack getItem() { return item; } + /** + * Gets the namespaced key used for this item. + * + * @return the namespaced key + * @since 0.10.8 + */ + @NotNull + @Contract(pure = true) + public NamespacedKey getKey() { + return keyUUID; + } + /** * Gets the {@link UUID} associated with this {@link GuiItem}. This is for internal use only, and should not be * used. diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiListener.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiListener.java index 0703a5e0b..86f5c0629 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiListener.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/GuiListener.java @@ -2,9 +2,11 @@ import com.github.stefvanschie.inventoryframework.gui.type.*; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; +import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil; import org.bukkit.Bukkit; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -13,12 +15,12 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.inventory.*; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.logging.Logger; /** * Listens to events for {@link Gui}s. Only one instance of this class gets constructed. @@ -28,12 +30,28 @@ */ public class GuiListener implements Listener { + /** + * The owning plugin of this listener. + */ + @NotNull + private final Plugin plugin; + /** * A collection of all {@link Gui} instances that have at least one viewer. */ @NotNull private final Set activeGuiInstances = new HashSet<>(); + /** + * Creates a new listener for all guis for the provided {@code plugin}. + * + * @param plugin the owning plugin of this listener + * @since 0.10.8 + */ + public GuiListener(@NotNull Plugin plugin) { + this.plugin = plugin; + } + /** * Handles clicks in inventories * @@ -49,7 +67,7 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { } InventoryView view = event.getView(); - Inventory inventory = view.getInventory(event.getRawSlot()); + Inventory inventory = InventoryViewUtil.getInstance().getInventory(view, event.getRawSlot()); if (inventory == null) { gui.callOnOutsideClick(event); @@ -57,132 +75,23 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { } gui.callOnGlobalClick(event); - if (inventory.equals(view.getTopInventory())) { + if (inventory.equals(InventoryViewUtil.getInstance().getTopInventory(view))) { gui.callOnTopClick(event); } else { gui.callOnBottomClick(event); } gui.click(event); - } - - /** - * Resets the items into the correct positions for anvil guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsAnvil(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof AnvilGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - ((AnvilGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for beacon guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsBeacon(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof BeaconGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - ((BeaconGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for cartography table guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsCartographyTable(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof CartographyTableGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - ((CartographyTableGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for enchanting table guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsEnchantingTable(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof EnchantingTableGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - ((EnchantingTableGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for grindstone guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsGrindstone(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof GrindstoneGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - ((GrindstoneGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for stonecutter guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsStonecutter(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - if (!(holder instanceof StonecutterGui) || !(event.getWhoClicked() instanceof Player)) { - return; - } + if (event.isCancelled()) { + Bukkit.getScheduler().runTask(this.plugin, () -> { + PlayerInventory playerInventory = event.getWhoClicked().getInventory(); - ((StonecutterGui) holder).handleClickEvent(event); - } - - /** - * Resets the items into the correct positions for smithing table guis - * - * @param event the event fired - * @since 0.8.0 - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void resetItemsSmithingTable(@NotNull InventoryClickEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - - if (!(holder instanceof SmithingTableGui) || !(event.getWhoClicked() instanceof Player)) { - return; + /* due to a client issue off-hand items appear as ghost items, this updates the off-hand correctly + client-side */ + playerInventory.setItemInOffHand(playerInventory.getItemInOffHand()); + }); } - - ((SmithingTableGui) holder).handleClickEvent(event); } /** @@ -193,17 +102,19 @@ public void resetItemsSmithingTable(@NotNull InventoryClickEvent event) { */ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onEntityPickupItem(@NotNull EntityPickupItemEvent event) { - if (!(event.getEntity() instanceof HumanEntity)) { + LivingEntity entity = event.getEntity(); + + if (!(entity instanceof HumanEntity)) { return; } - Gui gui = getGui(((HumanEntity) event.getEntity()).getOpenInventory().getTopInventory()); + Gui gui = getGui(InventoryViewUtil.getInstance().getTopInventory(((HumanEntity) entity).getOpenInventory())); if (gui == null || !gui.isPlayerInventoryUsed()) { return; } - int leftOver = gui.getHumanEntityCache().add((HumanEntity) event.getEntity(), event.getItem().getItemStack()); + int leftOver = gui.getHumanEntityCache().add((HumanEntity) entity, event.getItem().getItemStack()); if (leftOver == 0) { event.getItem().remove(); @@ -233,17 +144,17 @@ public void onInventoryDrag(@NotNull InventoryDragEvent event) { } InventoryView view = event.getView(); - Set inventorySlots = event.getInventorySlots(); + Set inventorySlots = event.getRawSlots(); if (inventorySlots.size() > 1) { boolean top = false, bottom = false; - for (int inventorySlot : event.getRawSlots()) { - Inventory inventory = view.getInventory(inventorySlot); + for (int inventorySlot : inventorySlots) { + Inventory inventory = InventoryViewUtil.getInstance().getInventory(view, inventorySlot); - if (view.getTopInventory().equals(inventory)) { + if (InventoryViewUtil.getInstance().getTopInventory(view).equals(inventory)) { top = true; - } else if (view.getBottomInventory().equals(inventory)) { + } else if (InventoryViewUtil.getInstance().getBottomInventory(view).equals(inventory)) { bottom = true; } @@ -263,23 +174,46 @@ public void onInventoryDrag(@NotNull InventoryDragEvent event) { } } else { int index = inventorySlots.toArray(new Integer[0])[0]; - InventoryType.SlotType slotType = view.getSlotType(index); + InventoryType.SlotType slotType = InventoryViewUtil.getInstance().getSlotType(view, index); boolean even = event.getType() == DragType.EVEN; ClickType clickType = even ? ClickType.LEFT : ClickType.RIGHT; InventoryAction inventoryAction = even ? InventoryAction.PLACE_SOME : InventoryAction.PLACE_ONE; + ItemStack previousViewCursor = InventoryViewUtil.getInstance().getCursor(view); + // Overwrite getCursor in inventory click event to mimic real event fired by Bukkit. + InventoryViewUtil.getInstance().setCursor(view, event.getOldCursor()); //this is a fake click event, firing this may cause other plugins to function incorrectly, so keep it local InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(view, slotType, index, clickType, inventoryAction); onInventoryClick(inventoryClickEvent); + // Restore previous cursor only if someone has not changed it manually in onInventoryClick. + if (Objects.equals(InventoryViewUtil.getInstance().getCursor(view), event.getOldCursor())) { + InventoryViewUtil.getInstance().setCursor(view, previousViewCursor); + } event.setCancelled(inventoryClickEvent.isCancelled()); } } + /** + * Handles the selection of trades in merchant guis + * + * @param event the event fired + */ + @EventHandler(ignoreCancelled = true) + public void onTradeSelect(@NotNull TradeSelectEvent event) { + Gui gui = getGui(event.getInventory()); + + if (!(gui instanceof MerchantGui)) { + return; + } + + ((MerchantGui) gui).callOnTradeSelect(event); + } + /** * Handles closing in inventories * @@ -290,7 +224,7 @@ public void onInventoryDrag(@NotNull InventoryDragEvent event) { public void onInventoryClose(@NotNull InventoryCloseEvent event) { Gui gui = getGui(event.getInventory()); - if (gui == null) { + if (gui == null || isNamedGuiUpdatingDirtily(gui)) { return; } @@ -300,22 +234,16 @@ public void onInventoryClose(@NotNull InventoryCloseEvent event) { //due to a client issue off-hand items appear as ghost items, this updates the off-hand correctly client-side playerInventory.setItemInOffHand(playerInventory.getItemInOffHand()); - if (gui.isUpdating()) { - gui.getHumanEntityCache().restoreAndForget(humanEntity); + gui.callOnClose(event); - if (gui.getViewerCount() == 1) { - activeGuiInstances.remove(gui); - } - } else { - gui.callOnClose(event); + gui.getHumanEntityCache().restoreAndForget(humanEntity); - //this is a hack to remove items correctly when players press the x button in a beacon - Bukkit.getScheduler().runTask(JavaPlugin.getProvidingPlugin(getClass()), () -> { - if (humanEntity.getOpenInventory().getTopInventory() instanceof PlayerInventory) { - humanEntity.closeInventory(); - } - }); + if (gui.getViewerCount() == 1) { + activeGuiInstances.remove(gui); } + + //Bukkit doesn't like it if you open an inventory while the previous one is being closed + Bukkit.getScheduler().runTask(this.plugin, () -> gui.navigateToParent(humanEntity)); } /** @@ -328,7 +256,7 @@ public void onInventoryClose(@NotNull InventoryCloseEvent event) { public void onInventoryOpen(@NotNull InventoryOpenEvent event) { Gui gui = getGui(event.getInventory()); - if (gui == null) { + if (gui == null || isNamedGuiUpdatingDirtily(gui)) { return; } @@ -343,8 +271,7 @@ public void onInventoryOpen(@NotNull InventoryOpenEvent event) { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onPluginDisable(@NotNull PluginDisableEvent event) { - Plugin thisPlugin = JavaPlugin.getProvidingPlugin(getClass()); - if (event.getPlugin() != thisPlugin) { + if (event.getPlugin() != this.plugin) { return; } @@ -359,8 +286,11 @@ public void onPluginDisable(@NotNull PluginDisableEvent event) { } if (counter == maxCount) { - thisPlugin.getLogger().warning("Unable to close GUIs on plugin disable: they keep getting opened " - + "(tried: " + maxCount + " times)"); + Logger logger = this.plugin.getLogger(); + + logger.warning( + "Unable to close GUIs on plugin disable: they keep getting opened (tried: " + maxCount + " times)" + ); } } @@ -388,4 +318,11 @@ private Gui getGui(@NotNull Inventory inventory) { return null; } + + private boolean isNamedGuiUpdatingDirtily(@NotNull Gui gui) { + boolean dirtyTitle = gui instanceof NamedGui && (((NamedGui) gui).isDirty()); + boolean dirtyRows = gui instanceof ChestGui && ((ChestGui) gui).isDirtyRows(); + return gui.isUpdating() && (dirtyTitle || dirtyRows); + } + } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/InventoryComponent.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/InventoryComponent.java index 9dd4d6d12..fa23496f5 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/InventoryComponent.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/InventoryComponent.java @@ -1,11 +1,14 @@ package com.github.stefvanschie.inventoryframework.gui; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.pane.Pane; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -122,10 +125,10 @@ public void display(@NotNull Inventory inventory, int offset) { * This will make each pane in this component render their items in this inventory component. The panes are * displayed according to their priority, with the lowest priority rendering first and the highest priority (note: * highest priority, not {@link Pane.Priority#HIGHEST} priority) rendering last. The items displayed in this - * inventory component will be put into the inventory found in {@link Gui#getInventory()}. The slots will be placed - * from the top-right to the bottom-left, continuing from left-to-right, top-to-bottom plus the specified offset. - * This ordering is different from the normal ordering of the indices of a {@link PlayerInventory}. See for the - * normal ordering of a {@link PlayerInventory}'s slots its documentation. + * inventory component will be put into the inventory found in {@link InventoryBased#getInventory()}. The slots will + * be placed from the top-right to the bottom-left, continuing from left-to-right, top-to-bottom plus the specified + * offset. This ordering is different from the normal ordering of the indices of a {@link PlayerInventory}. See for + * the normal ordering of a {@link PlayerInventory}'s slots its documentation. * * @param inventory the inventory to place the items in * @param offset the offset from which to start counting the slots @@ -195,7 +198,7 @@ public void placeItems(@NotNull Inventory inventory, int offset) { * @since 0.8.0 */ public void click(@NotNull Gui gui, @NotNull InventoryClickEvent event, int slot) { - List panes = getPanes(); + List panes = new ArrayList<>(getPanes()); //loop panes in reverse, because the highest priority pane (last in list) is most likely to have the right item for (int i = panes.size() - 1; i >= 0; i--) { @@ -297,8 +300,23 @@ public InventoryComponent excludeRows(int from, int end) { * @param instance the instance to apply field and method references on * @param element the element to load * @since 0.8.0 + * @deprecated superseded by {@link #load(Object, Element, Plugin)} */ + @Deprecated public void load(@NotNull Object instance, @NotNull Element element) { + load(instance, element, JavaPlugin.getProvidingPlugin(InventoryComponent.class)); + } + + /** + * Loads the provided element's child panes onto this component. If the element contains any child panes, this will + * mutate this component. + * + * @param instance the instance to apply field and method references on + * @param element the element to load + * @param plugin the plugin to load the panes with + * @since 0.10.12 + */ + public void load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { NodeList childNodes = element.getChildNodes(); for (int innerIndex = 0; innerIndex < childNodes.getLength(); innerIndex++) { @@ -308,7 +326,7 @@ public void load(@NotNull Object instance, @NotNull Element element) { continue; } - addPane(Gui.loadPane(instance, innerItem)); + addPane(Gui.loadPane(instance, innerItem, plugin)); } } @@ -417,9 +435,10 @@ public void setItem(@NotNull GuiItem guiItem, int x, int y) { "; should be below " + getLength() + " and " + getHeight()); } - guiItem.applyUUID(); + GuiItem copy = guiItem.copy(); + copy.applyUUID(); - this.items[x][y] = guiItem.getItem(); + this.items[x][y] = copy.getItem(); } /** diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/AnvilGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/AnvilGui.java index 2bec9e2d6..cbf70d71d 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/AnvilGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/AnvilGui.java @@ -1,18 +1,21 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +29,23 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.logging.Level; /** * Represents a gui in the form of an anvil * * @since 0.8.0 */ -public class AnvilGui extends NamedGui { +public class AnvilGui extends NamedGui implements InventoryBased { + + /** + * Called whenever the name input is changed. + */ + @NotNull + private Consumer onNameInputChanged = (name) -> {}; /** * Represents the inventory component for the first item @@ -62,8 +75,7 @@ public class AnvilGui extends NamedGui { * An internal anvil inventory */ @NotNull - private final AnvilInventory anvilInventory = VersionMatcher.newAnvilInventory(Version.getVersion(), - this); + private final AnvilInventory anvilInventory = VersionMatcher.newAnvilInventory(Version.getVersion()); /** * Constructs a new anvil gui @@ -73,40 +85,127 @@ public class AnvilGui extends NamedGui { */ public AnvilGui(@NotNull String title) { super(title); + + this.anvilInventory.subscribeToNameInputChanges(this::callOnRename); + } + + /** + * Constructs a new anvil gui + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public AnvilGui(@NotNull TextHolder title) { + super(title); + + this.anvilInventory.subscribeToNameInputChanges(this::callOnRename); + } + + /** + * Constructs a new anvil gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #AnvilGui(String) + * @since 0.10.8 + */ + public AnvilGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + + this.anvilInventory.subscribeToNameInputChanges(this::callOnRename); + } + + /** + * Constructs a new anvil gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #AnvilGui(TextHolder) + * @since 0.10.8 + */ + public AnvilGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + + this.anvilInventory.subscribeToNameInputChanges(this::callOnRename); } @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Anvils can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getFirstItemComponent().display(getInventory(), 0); getSecondItemComponent().display(getInventory(), 1); getResultComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - anvilInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public AnvilGui copy() { - AnvilGui gui = new AnvilGui(getTitle()); + AnvilGui gui = new AnvilGui(getTitleHolder(), super.plugin); gui.firstItemComponent = firstItemComponent.copy(); gui.secondItemComponent = secondItemComponent.copy(); @@ -137,11 +236,42 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + + /** + * Sets the enchantment level cost for this anvil gui. Taking the item from the result slot will not actually remove + * these levels. Having a cost specified does not impede a player's ability to take the item in the result item, + * even if the player does not have the specified amount of levels. The cost must be a non-negative number. + * + * @param cost the cost + * @since 0.10.8 + * @throws IllegalArgumentException when the cost is less than zero + */ + public void setCost(short cost) { + if (cost < 0){ + throw new IllegalArgumentException("Cost must be non-negative"); + } + + this.anvilInventory.setCost(cost); + } + @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.ANVIL, title); + public Inventory createInventory() { + Inventory inventory = this.anvilInventory.createInventory(getTitleHolder()); + + addInventory(inventory, this); + + return inventory; } /** @@ -163,38 +293,45 @@ public boolean isPlayerInventoryUsed() { return getPlayerInventoryComponent().hasItem(); } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** - * Handles an incoming inventory click event + * Sets the consumer that should be called whenever the name input is changed. The argument is the new input. When + * this consumer is invoked, the value as returned by {@link #getRenameText()} will not have updated yet, hence + * allowing you to see the old value via that. * - * @param event the event to handle - * @since 0.8.0 + * @param onNameInputChanged the consumer to call when the rename input is changed + * @since 0.10.10 */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); - - if (slot >= 3 && slot <= 38) { - anvilInventory.sendItems(player, getTopItems()); - } else if (slot == 0 || slot == 1) { - if (event.isCancelled()) { - if (slot == 0) { - anvilInventory.sendFirstItem(player, getFirstItemComponent().getItem(0, 0)); - } else { - anvilInventory.sendSecondItem(player, getSecondItemComponent().getItem(0, 0)); - } - - anvilInventory.clearCursor(player); - } - - anvilInventory.sendResultItem(player, getResultComponent().getItem(0, 0)); - } else if (slot == 2 && !event.isCancelled()) { - anvilInventory.clearResultItem(player); + public void setOnNameInputChanged(@NotNull Consumer onNameInputChanged) { + this.onNameInputChanged = onNameInputChanged; + } - ItemStack resultItem = getResultComponent().getItem(0, 0); + /** + * Calls the consumer that was specified using {@link #setOnNameInputChanged(Consumer)}, so the consumer that should + * be called whenever the rename input is changed. Catches and logs all exceptions the consumer might throw. + * + * @param newInput the new rename input + * @since 0.10.10 + */ + private void callOnRename(@NotNull String newInput) { + try { + this.onNameInputChanged.accept(newInput); + } catch (Throwable throwable) { + String message = "Exception while handling onRename, newInput='" + newInput + "'"; - if (resultItem != null) { - anvilInventory.setCursor(player, resultItem); - } + this.plugin.getLogger().log(Level.SEVERE, message, throwable); } } @@ -267,19 +404,21 @@ private ItemStack[] getTopItems() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded anvil gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static AnvilGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static AnvilGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -291,16 +430,18 @@ public static AnvilGui load(@NotNull Object instance, @NotNull InputStream input * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will own the created gui * @return the loaded anvil gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static AnvilGui load(@NotNull Object instance, @NotNull Element element) { + public static AnvilGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - AnvilGui anvilGui = new AnvilGui(element.getAttribute("title")); + AnvilGui anvilGui = new AnvilGui(element.getAttribute("title"), plugin); anvilGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -345,9 +486,36 @@ public static AnvilGui load(@NotNull Object instance, @NotNull Element element) throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return anvilGui; } + + /** + * Loads an anvil gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded anvil gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static AnvilGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(AnvilGui.class)); + } + + /** + * Loads an anvil gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded anvil gui + * @since 0.8.0 + */ + @NotNull + public static AnvilGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(AnvilGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BarrelGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BarrelGui.java index 10815fedb..2c809eb3e 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BarrelGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BarrelGui.java @@ -1,16 +1,22 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.MergedGui; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.pane.Pane; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,6 +30,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +40,7 @@ * * @since 0.8.0 */ -public class BarrelGui extends NamedGui implements MergedGui { +public class BarrelGui extends NamedGui implements MergedGui, InventoryBased { /** * Represents the inventory component for the entire gui @@ -51,38 +58,117 @@ public BarrelGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public BarrelGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new barrel gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BarrelGui(String) + * @since 0.10.8 + */ + public BarrelGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new barrel gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BarrelGui(TextHolder) + * @since 0.10.8 + */ + public BarrelGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); int height = getInventoryComponent().getHeight(); getInventoryComponent().display(); + getInventoryComponent().excludeRows(height - 4, height - 1).placeItems(getInventory(), 0); - InventoryComponent topComponent = getInventoryComponent().excludeRows(height - 4, height - 1); - InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - topComponent.placeItems(getInventory(), 0); - bottomComponent.placeItems(humanEntity.getInventory(), 0); + populateBottomInventory(viewer); - if (bottomComponent.hasItem()) { - humanEntity.getInventory().clear(); + viewer.setItemOnCursor(cursor); + } - bottomComponent.placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + int height = getInventoryComponent().getHeight(); + InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + + if (bottomComponent.hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + bottomComponent.placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public BarrelGui copy() { - BarrelGui gui = new BarrelGui(getTitle()); + BarrelGui gui = new BarrelGui(getTitleHolder(), super.plugin); gui.inventoryComponent = inventoryComponent.copy(); @@ -95,6 +181,16 @@ public BarrelGui copy() { return gui; } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Override public void click(@NotNull InventoryClickEvent event) { getInventoryComponent().click(this, event, event.getRawSlot()); @@ -128,8 +224,21 @@ public Collection getItems() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.BARREL, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.BARREL); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } @NotNull @@ -144,19 +253,21 @@ public InventoryComponent getInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded barrel gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static BarrelGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static BarrelGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -168,16 +279,18 @@ public static BarrelGui load(@NotNull Object instance, @NotNull InputStream inpu * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded barrel gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static BarrelGui load(@NotNull Object instance, @NotNull Element element) { + public static BarrelGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - BarrelGui barrelGui = new BarrelGui(element.getAttribute("title")); + BarrelGui barrelGui = new BarrelGui(element.getAttribute("title"), plugin); barrelGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -197,9 +310,9 @@ public static BarrelGui load(@NotNull Object instance, @NotNull Element element) InventoryComponent inventoryComponent = barrelGui.getInventoryComponent(); if (componentElement.getTagName().equalsIgnoreCase("component")) { - inventoryComponent.load(instance, componentElement); + inventoryComponent.load(instance, componentElement, plugin); } else { - inventoryComponent.load(instance, element); + inventoryComponent.load(instance, element, plugin); } break; @@ -207,4 +320,31 @@ public static BarrelGui load(@NotNull Object instance, @NotNull Element element) return barrelGui; } + + /** + * Loads a barrel gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded barrel gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static BarrelGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(BarrelGui.class)); + } + + /** + * Loads a barrel gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded barrel gui + * @since 0.8.0 + */ + @NotNull + public static BarrelGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(BarrelGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BeaconGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BeaconGui.java index d88ae7419..52fcb5a11 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BeaconGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BeaconGui.java @@ -1,17 +1,20 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -25,13 +28,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a beacon * * @since 0.8.0 */ -public class BeaconGui extends Gui { +public class BeaconGui extends Gui implements InventoryBased { /** * Represents the payment item inventory component @@ -49,38 +54,87 @@ public class BeaconGui extends Gui { * An internal beacon inventory */ @NotNull - private final BeaconInventory beaconInventory = VersionMatcher.newBeaconInventory(Version.getVersion(), - this); + private final BeaconInventory beaconInventory = VersionMatcher.newBeaconInventory(Version.getVersion()); + + /** + * Constructs a new beacon gui. + * + * @since 0.8.0 + */ + public BeaconGui() { + this(JavaPlugin.getProvidingPlugin(BeaconGui.class)); + } + + /** + * Constructs a new beacon gui for the given {@code plugin}. + * + * @param plugin the owning plugin of this gui + * @see #BeaconGui() + * @since 0.10.8 + */ + public BeaconGui(@NotNull Plugin plugin) { + super(plugin); + } @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Beacons can only be opened by players"); - } + public void update() { + super.updating = true; getInventory().clear(); - getHumanEntityCache().storeAndClear(humanEntity); - getPaymentItemComponent().display(getInventory(), 0); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().display(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } - beaconInventory.openInventory((Player) humanEntity, getPaymentItemComponent().getItem(0, 0)); + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } } @NotNull @Contract(pure = true) @Override public BeaconGui copy() { - BeaconGui gui = new BeaconGui(); + BeaconGui gui = new BeaconGui(super.plugin); gui.paymentItemComponent = paymentItemComponent.copy(); gui.playerInventoryComponent = playerInventoryComponent.copy(); @@ -105,6 +159,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -115,26 +179,24 @@ public boolean isPlayerInventoryUsed() { @Contract(pure = true) @Override public Inventory createInventory() { - return Bukkit.createInventory(this, InventoryType.BEACON); - } + Inventory inventory = this.beaconInventory.createInventory(); - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); + addInventory(inventory, this); - if (slot >= 1 && slot <= 36) { - beaconInventory.sendItem(player, getPaymentItemComponent().getItem(0, 0)); - } else if (slot == 0 && event.isCancelled()) { - beaconInventory.sendItem(player, getPaymentItemComponent().getItem(0, 0)); + return inventory; + } - beaconInventory.clearCursor(player); - } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -166,19 +228,21 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded beacon gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static BeaconGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static BeaconGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -190,16 +254,14 @@ public static BeaconGui load(@NotNull Object instance, @NotNull InputStream inpu * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded beacon gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static BeaconGui load(@NotNull Object instance, @NotNull Element element) { - if (!element.hasAttribute("title")) { - throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); - } - - BeaconGui beaconGui = new BeaconGui(); + public static BeaconGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + BeaconGui beaconGui = new BeaconGui(plugin); beaconGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -238,9 +300,36 @@ public static BeaconGui load(@NotNull Object instance, @NotNull Element element) throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return beaconGui; } + + /** + * Loads a beacon gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded beacon gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static BeaconGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(BeaconGui.class)); + } + + /** + * Loads a beacon gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded beacon gui + * @since 0.8.0 + */ + @NotNull + public static BeaconGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(BeaconGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BlastFurnaceGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BlastFurnaceGui.java index dc24e3e0d..8d2cf238b 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BlastFurnaceGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BlastFurnaceGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a blast furnace * * @since 0.8.0 */ -public class BlastFurnaceGui extends NamedGui { +public class BlastFurnaceGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the ingredient @@ -63,33 +71,114 @@ public BlastFurnaceGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public BlastFurnaceGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new blast furnace gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BlastFurnaceGui(String) + * @since 0.10.8 + */ + public BlastFurnaceGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new blast furnace gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BlastFurnaceGui(TextHolder) + * @since 0.10.8 + */ + public BlastFurnaceGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } - getHumanEntityCache().store(humanEntity); + getInventory().clear(); getIngredientComponent().display(getInventory(), 0); getFuelComponent().display(getInventory(), 1); getOutputComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public BlastFurnaceGui copy() { - BlastFurnaceGui gui = new BlastFurnaceGui(getTitle()); + BlastFurnaceGui gui = new BlastFurnaceGui(getTitleHolder(), super.plugin); gui.ingredientComponent = ingredientComponent.copy(); gui.fuelComponent = fuelComponent.copy(); @@ -120,6 +209,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -129,8 +228,21 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.BLAST_FURNACE, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.BLAST_FURNACE); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -186,19 +298,22 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded blast furnace gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static BlastFurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static BlastFurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -210,16 +325,18 @@ public static BlastFurnaceGui load(@NotNull Object instance, @NotNull InputStrea * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded blast furnace gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static BlastFurnaceGui load(@NotNull Object instance, @NotNull Element element) { + public static BlastFurnaceGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - BlastFurnaceGui blastFurnaceGui = new BlastFurnaceGui(element.getAttribute("title")); + BlastFurnaceGui blastFurnaceGui = new BlastFurnaceGui(element.getAttribute("title"), plugin); blastFurnaceGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -264,9 +381,36 @@ public static BlastFurnaceGui load(@NotNull Object instance, @NotNull Element el throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return blastFurnaceGui; } + + /** + * Loads a blast furnace gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded blast furnace gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static BlastFurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(BlastFurnaceGui.class)); + } + + /** + * Loads a blast furnace gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded blast furnace gui + * @since 0.8.0 + */ + @NotNull + public static BlastFurnaceGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(BlastFurnaceGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BrewingStandGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BrewingStandGui.java index 2d9960f26..6e259ff18 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BrewingStandGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/BrewingStandGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a brewing stand * * @since 0.8.0 */ -public class BrewingStandGui extends NamedGui { +public class BrewingStandGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the first bottle @@ -75,11 +83,56 @@ public BrewingStandGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public BrewingStandGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new brewing stand gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BrewingStandGui(String) + * @since 0.10.8 + */ + public BrewingStandGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new brewing stand gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #BrewingStandGui(TextHolder) + * @since 0.10.8 + */ + public BrewingStandGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } - getHumanEntityCache().store(humanEntity); + getInventory().clear(); getFirstBottleComponent().display(getInventory(), 0); getSecondBottleComponent().display(getInventory(), 1); @@ -88,22 +141,57 @@ public void show(@NotNull HumanEntity humanEntity) { getBlazePowderComponent().display(getInventory(), 4); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + super.updating = true; - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public BrewingStandGui copy() { - BrewingStandGui gui = new BrewingStandGui(getTitle()); + BrewingStandGui gui = new BrewingStandGui(getTitleHolder(), super.plugin); gui.firstBottleComponent = firstBottleComponent.copy(); gui.secondBottleComponent = secondBottleComponent.copy(); @@ -146,17 +234,40 @@ public boolean isPlayerInventoryUsed() { return getPlayerInventoryComponent().hasItem(); } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - Inventory inventory = Bukkit.createInventory(this, InventoryType.BREWING, title); + public Inventory createInventory() { + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.BREWING); addInventory(inventory, this); return inventory; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** * Gets the inventory component representing the first bottle * @@ -234,19 +345,22 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded brewing stand gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static BrewingStandGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static BrewingStandGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -258,11 +372,13 @@ public static BrewingStandGui load(@NotNull Object instance, @NotNull InputStrea * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded brewing stand gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static BrewingStandGui load(@NotNull Object instance, @NotNull Element element) { + public static BrewingStandGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } @@ -318,9 +434,36 @@ public static BrewingStandGui load(@NotNull Object instance, @NotNull Element el throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return brewingStandGui; } + + /** + * Loads a brewing stand gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded brewing stand gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static BrewingStandGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(BrewingStandGui.class)); + } + + /** + * Loads a brewing stand gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded brewing stand gui + * @since 0.8.0 + */ + @NotNull + public static BrewingStandGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(BrewingStandGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CartographyTableGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CartographyTableGui.java index 14e2d9e0f..b67bc89d6 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CartographyTableGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CartographyTableGui.java @@ -1,18 +1,20 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -27,13 +29,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a cartography table * * @since 0.8.0 */ -public class CartographyTableGui extends NamedGui { +public class CartographyTableGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the map @@ -64,7 +68,7 @@ public class CartographyTableGui extends NamedGui { */ @NotNull private final CartographyTableInventory cartographyTableInventory = VersionMatcher.newCartographyTableInventory( - Version.getVersion(), this + Version.getVersion() ); /** @@ -77,37 +81,114 @@ public CartographyTableGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public CartographyTableGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new cartography table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #CartographyTableGui(String) + * @since 0.10.8 + */ + public CartographyTableGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new cartography table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #CartographyTableGui(TextHolder) + * @since 0.10.8 + */ + public CartographyTableGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Cartography tables can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getMapComponent().display(getInventory(), 0); getPaperComponent().display(getInventory(), 1); getOutputComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - cartographyTableInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public CartographyTableGui copy() { - CartographyTableGui gui = new CartographyTableGui(getTitle()); + CartographyTableGui gui = new CartographyTableGui(getTitleHolder(), super.plugin); gui.mapComponent = mapComponent.copy(); gui.paperComponent = paperComponent.copy(); @@ -138,6 +219,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -147,31 +238,25 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.CARTOGRAPHY, title); + public Inventory createInventory() { + Inventory inventory = this.cartographyTableInventory.createInventory(getTitleHolder()); + + addInventory(inventory, this); + + return inventory; } - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); - - if (slot >= 3 && slot <= 38) { - cartographyTableInventory.sendItems(player, getTopItems()); - } else if (slot >= 0 && slot <= 2) { - //the client rejects the output item if send immediately - Bukkit.getScheduler().runTask(JavaPlugin.getProvidingPlugin(getClass()), () -> - cartographyTableInventory.sendItems(player, getTopItems())); - - if (event.isCancelled()) { - cartographyTableInventory.clearCursor(player); - } - } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -222,40 +307,27 @@ public InventoryComponent getPlayerInventoryComponent() { return playerInventoryComponent; } - /** - * Gets the top items - * - * @return the top items - * @since 0.8.0 - */ - @Nullable - @Contract(pure = true) - private ItemStack[] getTopItems() { - return new ItemStack[] { - getMapComponent().getItem(0, 0), - getPaperComponent().getItem(0, 0), - getOutputComponent().getItem(0, 0) - }; - } - /** * Loads a cartography table gui from an XML file. * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded cartography table gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static CartographyTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static CartographyTableGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -267,16 +339,18 @@ public static CartographyTableGui load(@NotNull Object instance, @NotNull InputS * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded cartography table gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static CartographyTableGui load(@NotNull Object instance, @NotNull Element element) { + public static CartographyTableGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - CartographyTableGui cartographyTableGui = new CartographyTableGui(element.getAttribute("title")); + CartographyTableGui cartographyTableGui = new CartographyTableGui(element.getAttribute("title"), plugin); cartographyTableGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -321,9 +395,36 @@ public static CartographyTableGui load(@NotNull Object instance, @NotNull Elemen throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return cartographyTableGui; } + + /** + * Loads a cartography table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded cartography table gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static CartographyTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(CartographyTableGui.class)); + } + + /** + * Loads a cartography table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded cartography table gui + * @since 0.8.0 + */ + @NotNull + public static CartographyTableGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(CartographyTableGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ChestGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ChestGui.java index 64930e34f..b6602b513 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ChestGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ChestGui.java @@ -1,15 +1,22 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.MergedGui; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.pane.Pane; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,6 +30,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +41,7 @@ * * @since 0.8.0 */ -public class ChestGui extends NamedGui implements MergedGui { +public class ChestGui extends NamedGui implements MergedGui, InventoryBased { /** * Represents the inventory component for the entire gui @@ -42,9 +50,9 @@ public class ChestGui extends NamedGui implements MergedGui { private InventoryComponent inventoryComponent; /** - * The amount of rows this gui has + * Whether the amount of rows are dirty i.e. has been changed */ - private int rows; + private boolean dirtyRows = false; /** * Constructs a new chest GUI @@ -54,7 +62,44 @@ public class ChestGui extends NamedGui implements MergedGui { * @since 0.8.0 */ public ChestGui(int rows, @NotNull String title) { - super(title); + this(rows, StringHolder.of(title), JavaPlugin.getProvidingPlugin(ChestGui.class)); + } + + /** + * Constructs a new chest GUI + * + * @param rows the amount of rows this gui should contain, in range 1..6. + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public ChestGui(int rows, @NotNull TextHolder title) { + this(rows, title, JavaPlugin.getProvidingPlugin(ChestGui.class)); + } + + /** + * Constructs a new chest gui for the given {@code plugin}. + * + * @param rows the amount of rows this gui should contain, in range 1..6. + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ChestGui(int, String) + * @since 0.10.8 + */ + public ChestGui(int rows, @NotNull String title, @NotNull Plugin plugin) { + this(rows, StringHolder.of(title), plugin); + } + + /** + * Constructs a new chest gui for the given {@code plugin}. + * + * @param rows the amount of rows this gui should contain, in range 1..6. + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ChestGui(int, TextHolder) + * @since 0.10.8 + */ + public ChestGui(int rows, @NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); if (!(rows >= 1 && rows <= 6)) { throw new IllegalArgumentException("Rows should be between 1 and 6"); @@ -64,36 +109,87 @@ public ChestGui(int rows, @NotNull String title) { } @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; + + if (isDirty() || dirtyRows) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + this.dirtyRows = false; + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } - getHumanEntityCache().store(humanEntity); + markChanges(); + } + + getInventory().clear(); int height = getInventoryComponent().getHeight(); getInventoryComponent().display(); + getInventoryComponent().excludeRows(height - 4, height - 1).placeItems(getInventory(), 0); - InventoryComponent topComponent = getInventoryComponent().excludeRows(height - 4, height - 1); InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); - topComponent.placeItems(getInventory(), 0); + HumanEntityCache humanEntityCache = getHumanEntityCache(); - if (bottomComponent.hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - bottomComponent.placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + int height = getInventoryComponent().getHeight(); + InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + + if (bottomComponent.hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + bottomComponent.placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public ChestGui copy() { - ChestGui gui = new ChestGui(getRows(), getTitle()); + ChestGui gui = new ChestGui(getRows(), getTitleHolder(), super.plugin); gui.inventoryComponent = inventoryComponent.copy(); @@ -130,14 +226,24 @@ public void setRows(int rows) { throw new IllegalArgumentException("Rows should be between 1 and 6"); } - this.inventoryComponent = new InventoryComponent(9, rows + 4); + InventoryComponent inventoryComponent = new InventoryComponent(9, rows + 4); - //copy the viewers - List viewers = getViewers(); + for (Pane pane : this.inventoryComponent.getPanes()) { + inventoryComponent.addPane(pane); + } - this.inventory = Bukkit.createInventory(this, rows * 9, getTitle()); + this.inventoryComponent = inventoryComponent; + this.dirtyRows = true; + } - viewers.forEach(humanEntity -> humanEntity.openInventory(inventory)); + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; } @Override @@ -162,8 +268,8 @@ public Collection getItems() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, getRows() * 9, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, getRows() * 9); } /** @@ -177,6 +283,19 @@ public int getRows() { return getInventoryComponent().getHeight() - 4; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + @NotNull @Contract(pure = true) @Override @@ -189,19 +308,21 @@ public InventoryComponent getInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded chest gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static ChestGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static ChestGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -213,11 +334,13 @@ public static ChestGui load(@NotNull Object instance, @NotNull InputStream input * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded chest gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static ChestGui load(@NotNull Object instance, @NotNull Element element) { + public static ChestGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } @@ -234,7 +357,7 @@ public static ChestGui load(@NotNull Object instance, @NotNull Element element) throw new XMLLoadException("Rows attribute is not an integer", exception); } - ChestGui chestGui = new ChestGui(rows, element.getAttribute("title")); + ChestGui chestGui = new ChestGui(rows, element.getAttribute("title"), plugin); chestGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -254,9 +377,9 @@ public static ChestGui load(@NotNull Object instance, @NotNull Element element) InventoryComponent inventoryComponent = chestGui.getInventoryComponent(); if (componentElement.getTagName().equalsIgnoreCase("component")) { - inventoryComponent.load(instance, componentElement); + inventoryComponent.load(instance, componentElement, plugin); } else { - inventoryComponent.load(instance, element); + inventoryComponent.load(instance, element, plugin); } break; @@ -264,4 +387,36 @@ public static ChestGui load(@NotNull Object instance, @NotNull Element element) return chestGui; } + + /** + * Loads a chest gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded chest gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static ChestGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(ChestGui.class)); + } + + /** + * Loads a chest gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded chest gui + * @since 0.8.0 + */ + @NotNull + public static ChestGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(ChestGui.class)); + } + + public boolean isDirtyRows() { + return dirtyRows; + } + } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CrafterGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CrafterGui.java new file mode 100644 index 000000000..ba8d3b027 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CrafterGui.java @@ -0,0 +1,370 @@ +package com.github.stefvanschie.inventoryframework.gui.type; + +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; +import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a gui in the form of a crafter. + * + * @since 0.10.13 + */ +public class CrafterGui extends NamedGui implements InventoryBased { + + /** + * Represents the inventory component for the input + */ + @NotNull + private InventoryComponent inputComponent = new InventoryComponent(3, 3); + + /** + * Represents the inventory component for the player inventory + */ + @NotNull + private InventoryComponent playerInventoryComponent = new InventoryComponent(9, 4); + + /** + * Constructs a new crafter gui. + * + * @param title the title/name of this gui + * @since 0.10.13 + */ + public CrafterGui(@NotNull String title) { + super(title); + } + + /** + * Constructs a new crafter gui. + * + * @param title the title/name of this gui + * @since 0.10.13 + */ + public CrafterGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new crafter gui for the given {@code plugin}. + * + * @param title the title/name of this gui + * @param plugin the owning plugin of this gui + * @see #CrafterGui(String) + * @since 0.10.13 + */ + public CrafterGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new crafter gui for the given {@code plugin}. + * + * @param title the title/name of this gui + * @param plugin the owning plugin of this gui + * @see #CrafterGui(TextHolder) + * @since 0.10.13 + */ + public CrafterGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + + @Override + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); + + getInputComponent().display(getInventory(), 0); + getPlayerInventoryComponent().display(); + + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + + @NotNull + @Contract(pure = true) + @Override + public CrafterGui copy() { + CrafterGui gui = new CrafterGui(getTitleHolder(), super.plugin); + + gui.inputComponent = inputComponent.copy(); + gui.playerInventoryComponent = playerInventoryComponent.copy(); + + gui.setOnTopClick(this.onTopClick); + gui.setOnBottomClick(this.onBottomClick); + gui.setOnGlobalClick(this.onGlobalClick); + gui.setOnOutsideClick(this.onOutsideClick); + gui.setOnClose(this.onClose); + + return gui; + } + + @Override + public void click(@NotNull InventoryClickEvent event) { + int rawSlot = event.getRawSlot(); + + if (rawSlot >= 0 && rawSlot <= 8) { + getInputComponent().click(this, event, rawSlot); + } else { + getPlayerInventoryComponent().click(this, event, rawSlot - 9); + } + } + + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + + @Contract(pure = true) + @Override + public boolean isPlayerInventoryUsed() { + return getPlayerInventoryComponent().hasItem(); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory() { + //noinspection UnstableApiUsage + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.CRAFTER); + + addInventory(inventory, this); + + return inventory; + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + + /** + * Gets the inventory component representing the input. + * + * @return the input component + * @since 0.10.13 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getInputComponent() { + return inputComponent; + } + + /** + * Gets the inventory component representing the player inventory. + * + * @return the player inventory component + * @since 0.10.13 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getPlayerInventoryComponent() { + return playerInventoryComponent; + } + + /** + * Loads a crafter gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded crafter gui + * @see #load(Object, InputStream) + * @since 0.10.13 + */ + @Nullable + @Contract(pure = true) + public static CrafterGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); + Element documentElement = document.getDocumentElement(); + + documentElement.normalize(); + + return load(instance, documentElement, plugin); + } catch (SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Loads a crafter gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded crafter gui + * @since 0.10.13 + */ + @NotNull + public static CrafterGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("title")) { + throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); + } + + CrafterGui crafterGui = new CrafterGui(element.getAttribute("title"), plugin); + crafterGui.initializeOrThrow(instance, element); + + if (element.hasAttribute("populate")) { + return crafterGui; + } + + NodeList childNodes = element.getChildNodes(); + + for (int index = 0; index < childNodes.getLength(); index++) { + Node item = childNodes.item(index); + + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element componentElement = (Element) item; + + if (!componentElement.getTagName().equalsIgnoreCase("component")) { + throw new XMLLoadException("Gui element contains non-component tags"); + } + + if (!componentElement.hasAttribute("name")) { + throw new XMLLoadException("Component tag does not have a name specified"); + } + + InventoryComponent component; + + switch (componentElement.getAttribute("name")) { + case "input": + component = crafterGui.getInputComponent(); + break; + case "player-inventory": + component = crafterGui.getPlayerInventoryComponent(); + break; + default: + throw new XMLLoadException("Unknown component name"); + } + + component.load(instance, componentElement, plugin); + } + + return crafterGui; + } + + /** + * Loads a crafter gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded crafter gui + * @since 0.10.13 + */ + @Nullable + @Contract(pure = true) + public static CrafterGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(CrafterGui.class)); + } + + /** + * Loads a crafter gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded crafting table gui + * @since 0.10.13 + */ + @NotNull + public static CrafterGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(CrafterGui.class)); + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CraftingTableGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CraftingTableGui.java index 735260584..7147a746b 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CraftingTableGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/CraftingTableGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a crafting table * * @since 0.8.0 */ -public class CraftingTableGui extends NamedGui { +public class CraftingTableGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the input @@ -57,32 +65,113 @@ public CraftingTableGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public CraftingTableGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new crafting table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #CraftingTableGui(String) + * @since 0.10.8 + */ + public CraftingTableGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new crafting table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #CraftingTableGui(TextHolder) + * @since 0.10.8 + */ + public CraftingTableGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } - getHumanEntityCache().store(humanEntity); + getInventory().clear(); getOutputComponent().display(getInventory(), 0); getInputComponent().display(getInventory(), 1); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public CraftingTableGui copy() { - CraftingTableGui gui = new CraftingTableGui(getTitle()); + CraftingTableGui gui = new CraftingTableGui(getTitleHolder(), super.plugin); gui.inputComponent = inputComponent.copy(); gui.outputComponent = outputComponent.copy(); @@ -110,6 +199,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -119,8 +218,21 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.WORKBENCH, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.WORKBENCH); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -164,19 +276,22 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded crafting table gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static CraftingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static CraftingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -188,16 +303,17 @@ public static CraftingTableGui load(@NotNull Object instance, @NotNull InputStre * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded crafting table gui - * @since 0.8.0 + * @since 0.10.8 */ @NotNull - public static CraftingTableGui load(@NotNull Object instance, @NotNull Element element) { + public static CraftingTableGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - CraftingTableGui craftingTableGui = new CraftingTableGui(element.getAttribute("title")); + CraftingTableGui craftingTableGui = new CraftingTableGui(element.getAttribute("title"), plugin); craftingTableGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -239,9 +355,36 @@ public static CraftingTableGui load(@NotNull Object instance, @NotNull Element e throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return craftingTableGui; } + + /** + * Loads a crafting table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded crafting table gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static CraftingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(CraftingTableGui.class)); + } + + /** + * Loads a crafting table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded crafting table gui + * @since 0.8.0 + */ + @NotNull + public static CraftingTableGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(CraftingTableGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DispenserGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DispenserGui.java index dafdd3262..5e51485b6 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DispenserGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DispenserGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a dispenser * * @since 0.8.0 */ -public class DispenserGui extends NamedGui { +public class DispenserGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the contents @@ -51,31 +59,112 @@ public DispenserGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public DispenserGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new dispenser gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #DispenserGui(String) + * @since 0.10.8 + */ + public DispenserGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new dispenser gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #DispenserGui(TextHolder) + * @since 0.10.8 + */ + public DispenserGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); getContentsComponent().display(getInventory(), 0); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public DispenserGui copy() { - DispenserGui gui = new DispenserGui(getTitle()); + DispenserGui gui = new DispenserGui(getTitleHolder(), super.plugin); gui.contentsComponent = contentsComponent.copy(); gui.playerInventoryComponent = playerInventoryComponent.copy(); @@ -100,6 +189,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -109,14 +208,27 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - Inventory inventory = Bukkit.createInventory(this, InventoryType.DISPENSER, title); + public Inventory createInventory() { + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.DISPENSER); addInventory(inventory, this); return inventory; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** * Gets the inventory component representing the contents * @@ -146,19 +258,22 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded dispenser gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static DispenserGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static DispenserGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -170,16 +285,18 @@ public static DispenserGui load(@NotNull Object instance, @NotNull InputStream i * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded dispenser gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static DispenserGui load(@NotNull Object instance, @NotNull Element element) { + public static DispenserGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - DispenserGui dispenserGui = new DispenserGui(element.getAttribute("title")); + DispenserGui dispenserGui = new DispenserGui(element.getAttribute("title"), plugin); dispenserGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -218,9 +335,36 @@ public static DispenserGui load(@NotNull Object instance, @NotNull Element eleme throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return dispenserGui; } + + /** + * Loads a dispenser gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded dispenser gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static DispenserGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(DispenserGui.class)); + } + + /** + * Loads a dispenser gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded dispenser gui + * @since 0.8.0 + */ + @NotNull + public static DispenserGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(DispenserGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DropperGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DropperGui.java index 2cbf45cd1..8af5b3a7c 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DropperGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/DropperGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a dropper * * @since 0.8.0 */ -public class DropperGui extends NamedGui { +public class DropperGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the contents @@ -51,31 +59,112 @@ public DropperGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public DropperGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new dropper gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #DropperGui(String) + * @since 0.10.8 + */ + public DropperGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new dropper gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #DropperGui(TextHolder) + * @since 0.10.8 + */ + public DropperGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); getContentsComponent().display(getInventory(), 0); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public DropperGui copy() { - DropperGui gui = new DropperGui(getTitle()); + DropperGui gui = new DropperGui(getTitleHolder(), super.plugin); gui.contentsComponent = contentsComponent.copy(); gui.playerInventoryComponent = playerInventoryComponent.copy(); @@ -100,6 +189,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -109,14 +208,27 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - Inventory inventory = Bukkit.createInventory(this, InventoryType.DROPPER, title); + public Inventory createInventory() { + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.DROPPER); addInventory(inventory, this); return inventory; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** * Gets the inventory component representing the contents * @@ -146,19 +258,21 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded dropper gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static DropperGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static DropperGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -170,16 +284,18 @@ public static DropperGui load(@NotNull Object instance, @NotNull InputStream inp * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded dropper gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static DropperGui load(@NotNull Object instance, @NotNull Element element) { + public static DropperGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - DropperGui dropperGui = new DropperGui(element.getAttribute("title")); + DropperGui dropperGui = new DropperGui(element.getAttribute("title"), plugin); dropperGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -218,9 +334,36 @@ public static DropperGui load(@NotNull Object instance, @NotNull Element element throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return dropperGui; } + + /** + * Loads a dropper gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded dropper gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static DropperGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(DropperGui.class)); + } + + /** + * Loads a dropper gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded dropper gui + * @since 0.8.0 + */ + @NotNull + public static DropperGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(DropperGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnchantingTableGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnchantingTableGui.java index d4e771b4d..208fb7874 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnchantingTableGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnchantingTableGui.java @@ -1,18 +1,21 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +29,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of an enchanting table * * @since 0.8.0 */ -public class EnchantingTableGui extends NamedGui { +public class EnchantingTableGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the input @@ -51,7 +56,7 @@ public class EnchantingTableGui extends NamedGui { */ @NotNull private final EnchantingTableInventory enchantingTableInventory = VersionMatcher.newEnchantingTableInventory( - Version.getVersion(), this); + Version.getVersion()); /** * Constructs a new GUI @@ -63,35 +68,112 @@ public EnchantingTableGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public EnchantingTableGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new enchanting table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #EnchantingTableGui(String) + * @since 0.10.8 + */ + public EnchantingTableGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new enchanting table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #EnchantingTableGui(TextHolder) + * @since 0.10.8 + */ + public EnchantingTableGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Enchanting tables can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getInputComponent().display(getInventory(), 0); getPlayerInventoryComponent().display(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - enchantingTableInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public EnchantingTableGui copy() { - EnchantingTableGui gui = new EnchantingTableGui(getTitle()); + EnchantingTableGui gui = new EnchantingTableGui(getTitleHolder(), super.plugin); gui.inputComponent = inputComponent.copy(); gui.playerInventoryComponent = playerInventoryComponent.copy(); @@ -116,6 +198,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -125,27 +217,25 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.ENCHANTING, title); - } + public Inventory createInventory() { + Inventory inventory = this.enchantingTableInventory.createInventory(getTitleHolder()); - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); + addInventory(inventory, this); - if (slot >= 2 && slot <= 37) { - enchantingTableInventory.sendItems(player, getTopItems()); - } else if ((slot == 0 || slot == 1) && event.isCancelled()) { - enchantingTableInventory.sendItems(player, getTopItems()); + return inventory; + } - enchantingTableInventory.clearCursor(player); - } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -172,39 +262,27 @@ public InventoryComponent getPlayerInventoryComponent() { return playerInventoryComponent; } - /** - * Gets the top items - * - * @return the top items - * @since 0.8.0 - */ - @Nullable - @Contract(pure = true) - private ItemStack[] getTopItems() { - return new ItemStack[] { - getInputComponent().getItem(0, 0), - getInputComponent().getItem(1, 0) - }; - } - /** * Loads an enchanting table gui from an XML file. * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded enchanting table gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static EnchantingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static EnchantingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -216,16 +294,18 @@ public static EnchantingTableGui load(@NotNull Object instance, @NotNull InputSt * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded enchanting table gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static EnchantingTableGui load(@NotNull Object instance, @NotNull Element element) { + public static EnchantingTableGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - EnchantingTableGui enchantingTableGui = new EnchantingTableGui(element.getAttribute("title")); + EnchantingTableGui enchantingTableGui = new EnchantingTableGui(element.getAttribute("title"), plugin); enchantingTableGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -264,9 +344,36 @@ public static EnchantingTableGui load(@NotNull Object instance, @NotNull Element throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return enchantingTableGui; } + + /** + * Loads an enchanting table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded enchanting table gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static EnchantingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(EnchantingTableGui.class)); + } + + /** + * Loads an enchanting table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded enchanting table gui + * @since 0.8.0 + */ + @NotNull + public static EnchantingTableGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(EnchantingTableGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnderChestGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnderChestGui.java index db47c007e..89efe4c51 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnderChestGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/EnderChestGui.java @@ -1,16 +1,22 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.MergedGui; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.pane.Pane; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,6 +30,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +40,7 @@ * * @since 0.8.0 */ -public class EnderChestGui extends NamedGui implements MergedGui { +public class EnderChestGui extends NamedGui implements MergedGui, InventoryBased { /** * Represents the inventory component for the entire gui @@ -51,38 +58,117 @@ public EnderChestGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public EnderChestGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new ender chest gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #EnderChestGui(String) + * @since 0.10.8 + */ + public EnderChestGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new ender chest gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #EnderChestGui(TextHolder) + * @since 0.10.8 + */ + public EnderChestGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); int height = getInventoryComponent().getHeight(); getInventoryComponent().display(); + getInventoryComponent().excludeRows(height - 4, height - 1).placeItems(getInventory(), 0); - InventoryComponent topComponent = getInventoryComponent().excludeRows(height - 4, height - 1); - InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - topComponent.placeItems(getInventory(), 0); - bottomComponent.placeItems(humanEntity.getInventory(), 0); + populateBottomInventory(viewer); - if (bottomComponent.hasItem()) { - humanEntity.getInventory().clear(); + viewer.setItemOnCursor(cursor); + } - bottomComponent.placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + int height = getInventoryComponent().getHeight(); + InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + + if (bottomComponent.hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + bottomComponent.placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public EnderChestGui copy() { - EnderChestGui gui = new EnderChestGui(getTitle()); + EnderChestGui gui = new EnderChestGui(getTitleHolder(), super.plugin); gui.inventoryComponent = inventoryComponent.copy(); @@ -95,6 +181,16 @@ public EnderChestGui copy() { return gui; } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -128,8 +224,21 @@ public Collection getItems() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.ENDER_CHEST, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.ENDER_CHEST); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } @NotNull @@ -144,19 +253,22 @@ public InventoryComponent getInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded ender chest gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static EnderChestGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static EnderChestGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -168,16 +280,18 @@ public static EnderChestGui load(@NotNull Object instance, @NotNull InputStream * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded ender chest gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static EnderChestGui load(@NotNull Object instance, @NotNull Element element) { + public static EnderChestGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - EnderChestGui enderChestGui = new EnderChestGui(element.getAttribute("title")); + EnderChestGui enderChestGui = new EnderChestGui(element.getAttribute("title"), plugin); enderChestGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -197,9 +311,9 @@ public static EnderChestGui load(@NotNull Object instance, @NotNull Element elem InventoryComponent inventoryComponent = enderChestGui.getInventoryComponent(); if (componentElement.getTagName().equalsIgnoreCase("component")) { - inventoryComponent.load(instance, componentElement); + inventoryComponent.load(instance, componentElement, plugin); } else { - inventoryComponent.load(instance, element); + inventoryComponent.load(instance, element, plugin); } break; @@ -207,4 +321,31 @@ public static EnderChestGui load(@NotNull Object instance, @NotNull Element elem return enderChestGui; } + + /** + * Loads an ender chest gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded ender chest gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static EnderChestGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(EnderChestGui.class)); + } + + /** + * Loads an ender chest gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded ender chest gui + * @since 0.8.0 + */ + @NotNull + public static EnderChestGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(EnderChestGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/FurnaceGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/FurnaceGui.java index 674aa2dae..f7e4d2b66 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/FurnaceGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/FurnaceGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a furnace * * @since 0.8.0 */ -public class FurnaceGui extends NamedGui { +public class FurnaceGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the ingredient @@ -63,33 +71,114 @@ public FurnaceGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public FurnaceGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new furnace gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #FurnaceGui(String) + * @since 0.10.8 + */ + public FurnaceGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new furnace gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #FurnaceGui(TextHolder) + * @since 0.10.8 + */ + public FurnaceGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); getIngredientComponent().display(getInventory(), 0); getFuelComponent().display(getInventory(), 1); getOutputComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public FurnaceGui copy() { - FurnaceGui gui = new FurnaceGui(getTitle()); + FurnaceGui gui = new FurnaceGui(getTitleHolder(), super.plugin); gui.ingredientComponent = ingredientComponent.copy(); gui.fuelComponent = fuelComponent.copy(); @@ -120,6 +209,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -129,14 +228,27 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - Inventory inventory = Bukkit.createInventory(this, InventoryType.FURNACE, title); + public Inventory createInventory() { + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.FURNACE); addInventory(inventory, this); return inventory; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** * Gets the inventory component representing the ingredient * @@ -190,19 +302,21 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded furnace gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static FurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static FurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -214,16 +328,18 @@ public static FurnaceGui load(@NotNull Object instance, @NotNull InputStream inp * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded furnace gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static FurnaceGui load(@NotNull Object instance, @NotNull Element element) { + public static FurnaceGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - FurnaceGui furnaceGui = new FurnaceGui(element.getAttribute("title")); + FurnaceGui furnaceGui = new FurnaceGui(element.getAttribute("title"), plugin); furnaceGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -268,9 +384,36 @@ public static FurnaceGui load(@NotNull Object instance, @NotNull Element element throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return furnaceGui; } + + /** + * Loads a furnace gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded furnace gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static FurnaceGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(FurnaceGui.class)); + } + + /** + * Loads a furnace gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded furnace gui + * @since 0.8.0 + */ + @NotNull + public static FurnaceGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(FurnaceGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/GrindstoneGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/GrindstoneGui.java index a7c665c6e..1032e1312 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/GrindstoneGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/GrindstoneGui.java @@ -1,18 +1,21 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +29,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a grindstone * * @since 0.8.0 */ -public class GrindstoneGui extends NamedGui { +public class GrindstoneGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the items @@ -56,8 +61,7 @@ public class GrindstoneGui extends NamedGui { * An internal grindstone inventory */ @NotNull - private final GrindstoneInventory grindstoneInventory = VersionMatcher.newGrindstoneInventory(Version.getVersion(), - this); + private final GrindstoneInventory grindstoneInventory = VersionMatcher.newGrindstoneInventory(Version.getVersion()); /** * Constructs a new GUI @@ -69,36 +73,113 @@ public GrindstoneGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public GrindstoneGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new grindstone gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #GrindstoneGui(String) + * @since 0.10.8 + */ + public GrindstoneGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new grindstone gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #GrindstoneGui(TextHolder) + * @since 0.10.8 + */ + public GrindstoneGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Grindstones can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getItemsComponent().display(getInventory(), 0); getResultComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - grindstoneInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public GrindstoneGui copy() { - GrindstoneGui gui = new GrindstoneGui(getTitle()); + GrindstoneGui gui = new GrindstoneGui(getTitleHolder(), super.plugin); gui.itemsComponent = itemsComponent.copy(); gui.resultComponent = resultComponent.copy(); @@ -126,6 +207,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -135,29 +226,25 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.GRINDSTONE, title); - } + public Inventory createInventory() { + Inventory inventory = this.grindstoneInventory.createInventory(getTitleHolder()); - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); + addInventory(inventory, this); - if (slot >= 3 && slot <= 38) { - grindstoneInventory.sendItems(player, getTopItems()); - } else if (slot >= 0 && slot <= 2) { - grindstoneInventory.sendItems(player, getTopItems()); + return inventory; + } - if (event.isCancelled()) { - grindstoneInventory.clearCursor(player); - } - } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -196,40 +283,27 @@ public InventoryComponent getPlayerInventoryComponent() { return playerInventoryComponent; } - /** - * Gets the top items - * - * @return the top items - * @since 0.8.0 - */ - @Nullable - @Contract(pure = true) - private ItemStack[] getTopItems() { - return new ItemStack[] { - getItemsComponent().getItem(0, 0), - getItemsComponent().getItem(0, 1), - getResultComponent().getItem(0, 0) - }; - } - /** * Loads a grindstone gui from an XML file. * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded furnace gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static GrindstoneGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static GrindstoneGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -241,16 +315,18 @@ public static GrindstoneGui load(@NotNull Object instance, @NotNull InputStream * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded grindstone gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static GrindstoneGui load(@NotNull Object instance, @NotNull Element element) { + public static GrindstoneGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - GrindstoneGui grindstoneGui = new GrindstoneGui(element.getAttribute("title")); + GrindstoneGui grindstoneGui = new GrindstoneGui(element.getAttribute("title"), plugin); grindstoneGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -292,9 +368,36 @@ public static GrindstoneGui load(@NotNull Object instance, @NotNull Element elem throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return grindstoneGui; } + + /** + * Loads a grindstone gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded furnace gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static GrindstoneGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(GrindstoneGui.class)); + } + + /** + * Loads a grindstone gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded grindstone gui + * @since 0.8.0 + */ + @NotNull + public static GrindstoneGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(GrindstoneGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/HopperGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/HopperGui.java index b33be09b7..3f909db7c 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/HopperGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/HopperGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a hopper * * @since 0.8.0 */ -public class HopperGui extends NamedGui { +public class HopperGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the slots @@ -51,31 +59,112 @@ public HopperGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public HopperGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new hopper gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #HopperGui(String) + * @since 0.10.8 + */ + public HopperGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new hopper gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #HopperGui(TextHolder) + * @since 0.10.8 + */ + public HopperGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); getSlotsComponent().display(getInventory(), 0); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public HopperGui copy() { - HopperGui gui = new HopperGui(getTitle()); + HopperGui gui = new HopperGui(getTitleHolder(), super.plugin); gui.slotsComponent = slotsComponent.copy(); gui.playerInventoryComponent = playerInventoryComponent.copy(); @@ -100,6 +189,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -109,14 +208,27 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - Inventory inventory = Bukkit.createInventory(this, InventoryType.HOPPER, title); + public Inventory createInventory() { + Inventory inventory = getTitleHolder().asInventoryTitle(this, InventoryType.HOPPER); addInventory(inventory, this); return inventory; } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + /** * Gets the inventory component for the slots * @@ -146,19 +258,21 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded hopper gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static HopperGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static HopperGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -170,16 +284,18 @@ public static HopperGui load(@NotNull Object instance, @NotNull InputStream inpu * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded hopper gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static HopperGui load(@NotNull Object instance, @NotNull Element element) { + public static HopperGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - HopperGui hopperGui = new HopperGui(element.getAttribute("title")); + HopperGui hopperGui = new HopperGui(element.getAttribute("title"), plugin); hopperGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -218,9 +334,36 @@ public static HopperGui load(@NotNull Object instance, @NotNull Element element) throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return hopperGui; } + + /** + * Loads a hopper gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded hopper gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static HopperGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(HopperGui.class)); + } + + /** + * Loads a hopper gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded hopper gui + * @since 0.8.0 + */ + @NotNull + public static HopperGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(HopperGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/MerchantGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/MerchantGui.java new file mode 100644 index 000000000..d8caa20e7 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/MerchantGui.java @@ -0,0 +1,603 @@ +package com.github.stefvanschie.inventoryframework.gui.type; + +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; +import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; +import com.github.stefvanschie.inventoryframework.pane.Pane; +import com.github.stefvanschie.inventoryframework.util.XMLUtil; +import com.github.stefvanschie.inventoryframework.util.version.Version; +import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.TradeSelectEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Represents a gui in the form of a merchant. + * + * @since 0.10.0 + */ +public class MerchantGui extends NamedGui implements InventoryBased { + + /** + * The consumer that will be called once a players selects a trade listed + * on the left side of the gui + */ + private Consumer onTradeSelect; + + /** + * Represents the inventory component for the input + */ + @NotNull + private InventoryComponent inputComponent = new InventoryComponent(2, 1); + + /** + * Represents the inventory component for the player inventory + */ + @NotNull + private InventoryComponent playerInventoryComponent = new InventoryComponent(9, 4); + + /** + * The trades of this merchant with their price differences. The differences are the difference between the new + * price and the original price. + */ + @NotNull + private final List> trades = new ArrayList<>(); + + /** + * The experience of this merchant. Values below zero indicate that the experience should be hidden. + */ + private int experience = -1; + + /** + * The level of this merchant. A value of zero indicates this villager doesn't have a level. + */ + private int level = 0; + + /** + * The internal merchant inventory + */ + @NotNull + private final MerchantInventory merchantInventory = VersionMatcher.newMerchantInventory(Version.getVersion()); + + /** + * Creates a merchant gui with the given title. + * + * @param title the title + * @since 0.10.0 + */ + public MerchantGui(@NotNull String title) { + this(StringHolder.of(title)); + } + + /** + * Creates a merchant gui with the given title. + * + * @param title the title + * @since 0.10.0 + */ + public MerchantGui(@NotNull TextHolder title) { + this(title, JavaPlugin.getProvidingPlugin(MerchantGui.class)); + } + + /** + * Constructs a new merchant gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #MerchantGui(String) + * @since 0.10.8 + */ + public MerchantGui(@NotNull String title, @NotNull Plugin plugin) { + this(StringHolder.of(title), plugin); + } + + /** + * Constructs a new merchant gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #MerchantGui(TextHolder) + * @since 0.10.8 + */ + public MerchantGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Set the consumer that should be called whenever a trade is selected + * in this gui. + * + * @param onTradeSelect the consumer that gets called + */ + public void setOnTradeSelect(@Nullable Consumer onTradeSelect) { + this.onTradeSelect = onTradeSelect; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnTradeSelect(Consumer)}, + * so the consumer that should be called whenever a trade is selected in this gui. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + */ + public void callOnTradeSelect(@NotNull TradeSelectEvent event) { + callCallback(onTradeSelect, event, "onTradeSelect"); + } + + @Override + protected void initializeOrThrow(@NotNull Object instance, @NotNull Element element) { + super.initializeOrThrow(instance, element); + + if (element.hasAttribute("onTradeSelect")) { + setOnTradeSelect(XMLUtil.loadOnEventAttribute(instance, + element, TradeSelectEvent.class, "onTradeSelect")); + } + } + + @Override + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); + + getInputComponent().display(getInventory(), 0); + getPlayerInventoryComponent().display(); + + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + if ((this.experience >= 0 || this.level > 0 || !this.trades.isEmpty()) && viewer instanceof Player) { + this.merchantInventory.sendMerchantOffers((Player) viewer, this.trades, this.level, this.experience); + } + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (!(humanEntity instanceof Player)) { + throw new IllegalArgumentException("Merchants can only be opened by players"); + } + + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + + if (this.experience >= 0 || this.level > 0 || !this.trades.isEmpty()) { + this.merchantInventory.sendMerchantOffers((Player) humanEntity, this.trades, this.level, this.experience); + } + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + + @NotNull + @Override + public Gui copy() { + MerchantGui gui = new MerchantGui(getTitleHolder(), super.plugin); + + gui.inputComponent = inputComponent.copy(); + gui.playerInventoryComponent = playerInventoryComponent.copy(); + + gui.experience = experience; + gui.level = level; + + for (Map.Entry trade : trades) { + MerchantRecipe originalRecipe = trade.getKey(); + + ItemStack result = originalRecipe.getResult().clone(); + int uses = originalRecipe.getUses(); + int maxUses = originalRecipe.getMaxUses(); + boolean experienceReward = originalRecipe.hasExperienceReward(); + int villagerExperience = originalRecipe.getVillagerExperience(); + float priceMultiplier = originalRecipe.getPriceMultiplier(); + + MerchantRecipe recipe = new MerchantRecipe( + result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier + ); + + for (ItemStack ingredient : originalRecipe.getIngredients()) { + recipe.addIngredient(ingredient.clone()); + } + + gui.trades.add(new AbstractMap.SimpleImmutableEntry<>(recipe, trade.getValue())); + } + + gui.setOnTopClick(this.onTopClick); + gui.setOnBottomClick(this.onBottomClick); + gui.setOnGlobalClick(this.onGlobalClick); + gui.setOnOutsideClick(this.onOutsideClick); + gui.setOnTradeSelect(this.onTradeSelect); + gui.setOnClose(this.onClose); + + return gui; + } + + @Override + public void click(@NotNull InventoryClickEvent event) { + int rawSlot = event.getRawSlot(); + + if (rawSlot >= 0 && rawSlot <= 1) { + getInputComponent().click(this, event, rawSlot); + } else if (rawSlot != 2) { + getPlayerInventoryComponent().click(this, event, rawSlot - 3); + } + } + + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory() { + Inventory inventory = this.merchantInventory.createInventory(getTitleHolder()); + + addInventory(inventory, this); + + return inventory; + } + + /** + * Adds a trade to this gui. The specified discount is the difference between the old price and the new price. For + * example, if a price was decreased from five to two, the discount would be three. + * + * @param recipe the recipe to add + * @param discount the discount + * @since 0.10.1 + */ + public void addTrade(@NotNull MerchantRecipe recipe, int discount) { + this.trades.add(new AbstractMap.SimpleImmutableEntry<>(recipe, -discount)); + } + + /** + * Sets the experience of this merchant gui. Setting the experience will make the experience bar visible, even if + * the amount of experience is zero. Note that if the level of this merchant gui has not been set via + * {@link #setLevel(int)} that the experience will always show as zero even when set to something else. Experience + * must be greater than or equal to zero. Attempting to set the experience to below zero will throw an + * {@link IllegalArgumentException}. + * + * @param experience the experience to set + * @since 0.10.1 + * @throws IllegalArgumentException when the experience is below zero + */ + public void setExperience(int experience) { + if (experience < 0) { + throw new IllegalArgumentException("Experience must be greater than or equal to zero"); + } + + this.experience = experience; + } + + /** + * Sets the level of this merchant gui. This is a value between one and five and will visibly change the gui by + * appending the level of the villager to the title. These are displayed as "Novice", "Apprentice", "Journeyman", + * "Expert" and "Master" respectively (when the player's locale is set to English). When an argument is supplied + * that is not within one and five, an {@link IllegalArgumentException} will be thrown. + * + * @param level the numeric level + * @since 0.10.1 + * @throws IllegalArgumentException when the level is not between one and five + */ + public void setLevel(int level) { + if (level < 0 || level > 5) { + throw new IllegalArgumentException("Level must be between one and five"); + } + + this.level = level; + } + + /** + * Adds a trade to this gui. This will not set a discount on the trade. For specifiying discounts, see + * {@link #addTrade(MerchantRecipe, int)}. + * + * @param recipe the recipe to add + * @since 0.10.0 + */ + public void addTrade(@NotNull MerchantRecipe recipe) { + addTrade(recipe, 0); + } + + @Override + public boolean isPlayerInventoryUsed() { + return getPlayerInventoryComponent().hasItem(); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + + /** + * Gets the inventory component representing the input + * + * @return the input component + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getInputComponent() { + return inputComponent; + } + + /** + * Gets the inventory component representing the player inventory + * + * @return the player inventory component + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getPlayerInventoryComponent() { + return playerInventoryComponent; + } + + /** + * Loads a merchant gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded merchant gui + * @see #load(Object, InputStream) + * @since 0.10.8 + */ + @Nullable + @Contract(pure = true) + public static MerchantGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); + Element documentElement = document.getDocumentElement(); + + documentElement.normalize(); + + return load(instance, documentElement, plugin); + } catch (SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Loads a merchant gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded merchant gui + * @see #load(Object, Element) + * @since 0.10.8 + */ + @NotNull + @Contract(pure = true) + public static MerchantGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("title")) { + throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); + } + + MerchantGui merchantGui = new MerchantGui(element.getAttribute("title"), plugin); + merchantGui.initializeOrThrow(instance, element); + + if (element.hasAttribute("populate")) { + return merchantGui; + } + + NodeList childNodes = element.getChildNodes(); + + for (int index = 0; index < childNodes.getLength(); index++) { + Node item = childNodes.item(index); + + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element nestedElement = (Element) item; + String tagName = nestedElement.getTagName(); + + if (tagName.equalsIgnoreCase("component")) { + if (!nestedElement.hasAttribute("name")) { + throw new XMLLoadException("Component tag does not have a name specified"); + } + + InventoryComponent component; + + switch (nestedElement.getAttribute("name")) { + case "input": + component = merchantGui.getInputComponent(); + break; + case "player-inventory": + component = merchantGui.getPlayerInventoryComponent(); + break; + default: + throw new XMLLoadException("Unknown component name"); + } + + component.load(instance, nestedElement, plugin); + } else if (tagName.equalsIgnoreCase("trade")) { + NodeList tradeNodes = nestedElement.getChildNodes(); + + List ingredients = new ArrayList<>(2); + ItemStack result = null; + + for (int tradeIndex = 0; tradeIndex < tradeNodes.getLength(); tradeIndex++) { + Node tradeNode = tradeNodes.item(tradeIndex); + + if (tradeNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element tradeElement = (Element) tradeNode; + + if (tradeElement.getTagName().equalsIgnoreCase("ingredient")) { + if (ingredients.size() >= 2) { + throw new XMLLoadException("Too many ingredients specified, must be no more than two"); + } + + NodeList ingredientNodes = tradeElement.getChildNodes(); + + for (int ingredientIndex = 0; ingredientIndex < ingredientNodes.getLength(); ingredientIndex++) { + Node ingredientNode = ingredientNodes.item(ingredientIndex); + + if (ingredientNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + ingredients.add(Pane.loadItem(instance, (Element) ingredientNode).getItem()); + } + } else if (tradeElement.getTagName().equalsIgnoreCase("result")) { + NodeList resultNodes = tradeElement.getChildNodes(); + + for (int resultIndex = 0; resultIndex < resultNodes.getLength(); resultIndex++) { + Node resultNode = resultNodes.item(resultIndex); + + if (resultNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + if (result != null) { + throw new XMLLoadException("Multiple results specified for the same trade"); + } + + result = Pane.loadItem(instance, (Element) resultNode).getItem(); + } + } else { + throw new XMLLoadException("Trade element is neither an ingredient nor a result"); + } + } + + if (result == null) { + throw new XMLLoadException("Trade must have a result specified"); + } + + if (ingredients.size() < 1) { + throw new XMLLoadException("Trade must have at least one ingredient"); + } + + MerchantRecipe recipe = new MerchantRecipe(result, Integer.MAX_VALUE); + + recipe.setIngredients(ingredients); + + merchantGui.addTrade(recipe); + } else { + throw new XMLLoadException("Nested element is neither a component nor a trade"); + } + } + + return merchantGui; + } + + /** + * Loads a merchant gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded merchant gui + * @since 0.10.0 + */ + @Nullable + @Contract(pure = true) + public static MerchantGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(MerchantGui.class)); + } + + /** + * Loads a merchant gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded merchant gui + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static MerchantGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(MerchantGui.class)); + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ModernSmithingTableGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ModernSmithingTableGui.java new file mode 100644 index 000000000..e3a8867da --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ModernSmithingTableGui.java @@ -0,0 +1,405 @@ +package com.github.stefvanschie.inventoryframework.gui.type; + +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; +import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; +import com.github.stefvanschie.inventoryframework.util.version.Version; +import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a gui in the form of a smithing table. This is the modern variant with three input slots, available in + * from Minecraft 1.19.4. + * + * @since 0.10.9 + */ +public class ModernSmithingTableGui extends NamedGui implements InventoryBased { + + /** + * Represents the inventory component for the input + */ + @NotNull + private InventoryComponent inputComponent = new InventoryComponent(3, 1); + + /** + * Represents the inventory component for the result + */ + @NotNull + private InventoryComponent resultComponent = new InventoryComponent(1, 1); + + /** + * Represents the inventory component for the player inventory + */ + @NotNull + private InventoryComponent playerInventoryComponent = new InventoryComponent(9, 4); + + /** + * An internal smithing inventory + */ + @NotNull + private final SmithingTableInventory smithingTableInventory = VersionMatcher.newModernSmithingTableInventory( + Version.getVersion() + ); + + /** + * Constructs a new GUI. + * + * @param title the title/name of this gui. + * @since 0.10.9 + */ + public ModernSmithingTableGui(@NotNull String title) { + super(title); + } + + /** + * Constructs a new GUI. + * + * @param title the title/name of this gui. + * @since 0.10.9 + */ + public ModernSmithingTableGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new smithing table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ModernSmithingTableGui(String) + * @since 0.10.9 + */ + public ModernSmithingTableGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new smithing table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ModernSmithingTableGui(TextHolder) + * @since 0.10.9 + */ + public ModernSmithingTableGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + + @Override + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); + + getInputComponent().display(getInventory(), 0); + getResultComponent().display(getInventory(), 3); + getPlayerInventoryComponent().display(); + + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + + @NotNull + @Contract(pure = true) + @Override + public ModernSmithingTableGui copy() { + ModernSmithingTableGui gui = new ModernSmithingTableGui(getTitleHolder(), super.plugin); + + gui.inputComponent = inputComponent.copy(); + gui.resultComponent = resultComponent.copy(); + gui.playerInventoryComponent = playerInventoryComponent.copy(); + + gui.setOnTopClick(this.onTopClick); + gui.setOnBottomClick(this.onBottomClick); + gui.setOnGlobalClick(this.onGlobalClick); + gui.setOnOutsideClick(this.onOutsideClick); + gui.setOnClose(this.onClose); + + return gui; + } + + @Override + public void click(@NotNull InventoryClickEvent event) { + int rawSlot = event.getRawSlot(); + + if (rawSlot >= 0 && rawSlot <= 2) { + getInputComponent().click(this, event, rawSlot); + } else if (rawSlot == 3) { + getResultComponent().click(this, event, 0); + } else { + getPlayerInventoryComponent().click(this, event, rawSlot - 4); + } + } + + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + + @Contract(pure = true) + @Override + public boolean isPlayerInventoryUsed() { + return getPlayerInventoryComponent().hasItem(); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory() { + Inventory inventory = this.smithingTableInventory.createInventory(getTitleHolder()); + + addInventory(inventory, this); + + return inventory; + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); + } + + /** + * Gets the inventory component representing the input items + * + * @return the input component + * @since 0.10.9 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getInputComponent() { + return inputComponent; + } + + /** + * Gets the inventory component representing the result + * + * @return the result component + * @since 0.10.9 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getResultComponent() { + return resultComponent; + } + + /** + * Gets the inventory component representing the player inventory + * + * @return the player inventory component + * @since 0.10.9 + */ + @NotNull + @Contract(pure = true) + public InventoryComponent getPlayerInventoryComponent() { + return playerInventoryComponent; + } + + /** + * Loads a smithing table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded smithing table gui + * @see #load(Object, InputStream) + * @since 0.10.9 + */ + @Nullable + @Contract(pure = true) + public static ModernSmithingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); + Element documentElement = document.getDocumentElement(); + + documentElement.normalize(); + + return load(instance, documentElement, plugin); + } catch (SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Loads a smithing table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui + * @return the loaded smithing table gui + * @since 0.10.9 + */ + @NotNull + public static ModernSmithingTableGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("title")) { + throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); + } + + ModernSmithingTableGui smithingTableGui = new ModernSmithingTableGui(element.getAttribute("title"), plugin); + smithingTableGui.initializeOrThrow(instance, element); + + if (element.hasAttribute("populate")) { + return smithingTableGui; + } + + NodeList childNodes = element.getChildNodes(); + + for (int index = 0; index < childNodes.getLength(); index++) { + Node item = childNodes.item(index); + + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element componentElement = (Element) item; + + if (!componentElement.getTagName().equalsIgnoreCase("component")) { + throw new XMLLoadException("Gui element contains non-component tags"); + } + + if (!componentElement.hasAttribute("name")) { + throw new XMLLoadException("Component tag does not have a name specified"); + } + + InventoryComponent component; + + switch (componentElement.getAttribute("name")) { + case "input": + component = smithingTableGui.getInputComponent(); + break; + case "result": + component = smithingTableGui.getResultComponent(); + break; + case "player-inventory": + component = smithingTableGui.getPlayerInventoryComponent(); + break; + default: + throw new XMLLoadException("Unknown component name"); + } + + component.load(instance, componentElement, plugin); + } + + return smithingTableGui; + } + + /** + * Loads a smithing table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded smithing table gui + * @since 0.10.9 + */ + @Nullable + @Contract(pure = true) + public static ModernSmithingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(ModernSmithingTableGui.class)); + } + + /** + * Loads a smithing table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded smithing table gui + * @since 0.10.9 + */ + @NotNull + public static ModernSmithingTableGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(ModernSmithingTableGui.class)); + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ShulkerBoxGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ShulkerBoxGui.java index f84ee3f6a..8a527dd87 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ShulkerBoxGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/ShulkerBoxGui.java @@ -1,16 +1,22 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.MergedGui; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.pane.Pane; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,6 +30,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +40,7 @@ * * @since 0.8.0 */ -public class ShulkerBoxGui extends NamedGui implements MergedGui { +public class ShulkerBoxGui extends NamedGui implements MergedGui, InventoryBased { /** * Represents the inventory component for the entire gui @@ -51,38 +58,117 @@ public ShulkerBoxGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public ShulkerBoxGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new shulker box gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ShulkerBoxGui(String) + * @since 0.10.8 + */ + public ShulkerBoxGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new shulker box gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #ShulkerBoxGui(TextHolder) + * @since 0.10.8 + */ + public ShulkerBoxGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; - getHumanEntityCache().store(humanEntity); + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } + + getInventory().clear(); int height = getInventoryComponent().getHeight(); getInventoryComponent().display(); + getInventoryComponent().excludeRows(height - 4, height - 1).placeItems(getInventory(), 0); - InventoryComponent topComponent = getInventoryComponent().excludeRows(height - 4, height - 1); - InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - topComponent.placeItems(getInventory(), 0); - bottomComponent.placeItems(humanEntity.getInventory(), 0); + populateBottomInventory(viewer); - if (bottomComponent.hasItem()) { - humanEntity.getInventory().clear(); + viewer.setItemOnCursor(cursor); + } - bottomComponent.placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + int height = getInventoryComponent().getHeight(); + InventoryComponent bottomComponent = getInventoryComponent().excludeRows(0, height - 5); + + if (bottomComponent.hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + bottomComponent.placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public ShulkerBoxGui copy() { - ShulkerBoxGui gui = new ShulkerBoxGui(getTitle()); + ShulkerBoxGui gui = new ShulkerBoxGui(getTitleHolder(), super.plugin); gui.inventoryComponent = inventoryComponent.copy(); @@ -95,6 +181,16 @@ public ShulkerBoxGui copy() { return gui; } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -128,8 +224,21 @@ public Collection getItems() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.SHULKER_BOX, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.SHULKER_BOX); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } @NotNull @@ -144,19 +253,22 @@ public InventoryComponent getInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded shulker box gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static ShulkerBoxGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static ShulkerBoxGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -168,16 +280,18 @@ public static ShulkerBoxGui load(@NotNull Object instance, @NotNull InputStream * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded shulker box gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static ShulkerBoxGui load(@NotNull Object instance, @NotNull Element element) { + public static ShulkerBoxGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - ShulkerBoxGui shulkerBoxGui = new ShulkerBoxGui(element.getAttribute("title")); + ShulkerBoxGui shulkerBoxGui = new ShulkerBoxGui(element.getAttribute("title"), plugin); shulkerBoxGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -197,9 +311,9 @@ public static ShulkerBoxGui load(@NotNull Object instance, @NotNull Element elem InventoryComponent inventoryComponent = shulkerBoxGui.getInventoryComponent(); if (componentElement.getTagName().equalsIgnoreCase("component")) { - inventoryComponent.load(instance, componentElement); + inventoryComponent.load(instance, componentElement, plugin); } else { - inventoryComponent.load(instance, element); + inventoryComponent.load(instance, element, plugin); } break; @@ -207,4 +321,31 @@ public static ShulkerBoxGui load(@NotNull Object instance, @NotNull Element elem return shulkerBoxGui; } + + /** + * Loads a shulker box gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded shulker box gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static ShulkerBoxGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(ShulkerBoxGui.class)); + } + + /** + * Loads a shulker box gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded shulker box gui + * @since 0.8.0 + */ + @NotNull + public static ShulkerBoxGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(ShulkerBoxGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmithingTableGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmithingTableGui.java index bb058e527..4f60e28a9 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmithingTableGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmithingTableGui.java @@ -1,18 +1,21 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +29,17 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** - * Represents a gui in the form of a smithing table + * Represents a gui in the form of a smithing table. This is the legacy variant with two input slots, available prior to + * Minecraft 1.20. * * @since 0.8.0 */ -public class SmithingTableGui extends NamedGui { +@Deprecated +public class SmithingTableGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the first item @@ -63,7 +70,7 @@ public class SmithingTableGui extends NamedGui { */ @NotNull private final SmithingTableInventory smithingTableInventory = VersionMatcher.newSmithingTableInventory( - Version.getVersion(), this + Version.getVersion() ); /** @@ -76,37 +83,114 @@ public SmithingTableGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public SmithingTableGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new smithing table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #SmithingTableGui(String) + * @since 0.10.8 + */ + public SmithingTableGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new smithing table gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #SmithingTableGui(TextHolder) + * @since 0.10.8 + */ + public SmithingTableGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Smithing tables can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getFirstItemComponent().display(getInventory(), 0); getSecondItemComponent().display(getInventory(), 1); getResultComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - smithingTableInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public SmithingTableGui copy() { - SmithingTableGui gui = new SmithingTableGui(getTitle()); + SmithingTableGui gui = new SmithingTableGui(getTitleHolder(), super.plugin); gui.firstItemComponent = firstItemComponent.copy(); gui.secondItemComponent = secondItemComponent.copy(); @@ -137,6 +221,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -146,43 +240,25 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.SMITHING, title); - } - - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); - - if (slot >= 3 && slot <= 38) { - smithingTableInventory.sendItems(player, getTopItems()); - } else if (slot == 0 || slot == 1) { - if (event.isCancelled()) { - if (slot == 0) { - smithingTableInventory.sendFirstItem(player, getFirstItemComponent().getItem(0, 0)); - } else { - smithingTableInventory.sendSecondItem(player, getSecondItemComponent().getItem(0, 0)); - } + public Inventory createInventory() { + Inventory inventory = this.smithingTableInventory.createInventory(getTitleHolder()); - smithingTableInventory.clearCursor(player); - } + addInventory(inventory, this); - smithingTableInventory.sendResultItem(player, getResultComponent().getItem(0, 0)); - } else if (slot == 2 && !event.isCancelled()) { - smithingTableInventory.clearResultItem(player); + return inventory; + } - ItemStack resultItem = getResultComponent().getItem(0, 0); + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } - if (resultItem != null) { - smithingTableInventory.setCursor(player, resultItem); - } - } + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -233,40 +309,27 @@ public InventoryComponent getPlayerInventoryComponent() { return playerInventoryComponent; } - /** - * Gets the top items - * - * @return the top items - * @since 0.8.0 - */ - @Nullable - @Contract(pure = true) - private ItemStack[] getTopItems() { - return new ItemStack[] { - getFirstItemComponent().getItem(0, 0), - getSecondItemComponent().getItem(0, 0), - getResultComponent().getItem(0, 0) - }; - } - /** * Loads a smithing table gui from an XML file. * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded smithing table gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static SmithingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static SmithingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -278,16 +341,17 @@ public static SmithingTableGui load(@NotNull Object instance, @NotNull InputStre * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded smithing table gui - * @since 0.8.0 + * @since 0.10.8 */ @NotNull - public static SmithingTableGui load(@NotNull Object instance, @NotNull Element element) { + public static SmithingTableGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - SmithingTableGui smithingTableGui = new SmithingTableGui(element.getAttribute("title")); + SmithingTableGui smithingTableGui = new SmithingTableGui(element.getAttribute("title"), plugin); smithingTableGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -332,9 +396,36 @@ public static SmithingTableGui load(@NotNull Object instance, @NotNull Element e throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return smithingTableGui; } + + /** + * Loads a smithing table gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded smithing table gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static SmithingTableGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(SmithingTableGui.class)); + } + + /** + * Loads a smithing table gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded smithing table gui + * @since 0.8.0 + */ + @NotNull + public static SmithingTableGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(SmithingTableGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmokerGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmokerGui.java index c65fd42b1..6b834ce28 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmokerGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/SmokerGui.java @@ -1,13 +1,19 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +27,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a smoker * * @since 0.8.0 */ -public class SmokerGui extends NamedGui { +public class SmokerGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the ingredient @@ -63,33 +71,114 @@ public SmokerGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public SmokerGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new smoker gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #SmokerGui(String) + * @since 0.10.8 + */ + public SmokerGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new smoker gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #SmokerGui(TextHolder) + * @since 0.10.8 + */ + public SmokerGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - getInventory().clear(); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); + } - getHumanEntityCache().store(humanEntity); + getInventory().clear(); getIngredientComponent().display(getInventory(), 0); getFuelComponent().display(getInventory(), 1); getOutputComponent().display(getInventory(), 2); getPlayerInventoryComponent().display(); - if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); - getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); } + populateBottomInventory(humanEntity); + humanEntity.openInventory(getInventory()); } + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { + if (getPlayerInventoryComponent().hasItem()) { + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } + + getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); + } + } + @NotNull @Contract(pure = true) @Override public SmokerGui copy() { - SmokerGui gui = new SmokerGui(getTitle()); + SmokerGui gui = new SmokerGui(getTitleHolder(), super.plugin); gui.ingredientComponent = ingredientComponent.copy(); gui.fuelComponent = fuelComponent.copy(); @@ -120,6 +209,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -129,8 +228,21 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.SMOKER, title); + public Inventory createInventory() { + return getTitleHolder().asInventoryTitle(this, InventoryType.SMOKER); + } + + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -186,19 +298,21 @@ public InventoryComponent getPlayerInventoryComponent() { * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded smoker gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static SmokerGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static SmokerGui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -210,16 +324,18 @@ public static SmokerGui load(@NotNull Object instance, @NotNull InputStream inpu * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded smoker gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static SmokerGui load(@NotNull Object instance, @NotNull Element element) { + public static SmokerGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - SmokerGui smokerGui = new SmokerGui(element.getAttribute("title")); + SmokerGui smokerGui = new SmokerGui(element.getAttribute("title"), plugin); smokerGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -264,9 +380,36 @@ public static SmokerGui load(@NotNull Object instance, @NotNull Element element) throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return smokerGui; } + + /** + * Loads a smoker gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded smoker gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static SmokerGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(SmokerGui.class)); + } + + /** + * Loads a smoker gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded smoker gui + * @since 0.8.0 + */ + @NotNull + public static SmokerGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(SmokerGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/StonecutterGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/StonecutterGui.java index 89d219181..9687bfd4e 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/StonecutterGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/StonecutterGui.java @@ -1,18 +1,21 @@ package com.github.stefvanschie.inventoryframework.gui.type; +import com.github.stefvanschie.inventoryframework.HumanEntityCache; import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.InventoryBased; import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui; import com.github.stefvanschie.inventoryframework.util.version.Version; import com.github.stefvanschie.inventoryframework.util.version.VersionMatcher; -import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +29,15 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * Represents a gui in the form of a stonecutter * * @since 0.8.0 */ -public class StonecutterGui extends NamedGui { +public class StonecutterGui extends NamedGui implements InventoryBased { /** * Represents the inventory component for the input @@ -57,7 +62,7 @@ public class StonecutterGui extends NamedGui { */ @NotNull private final StonecutterInventory stonecutterInventory = VersionMatcher.newStonecutterInventory( - Version.getVersion(), this + Version.getVersion() ); /** @@ -70,36 +75,113 @@ public StonecutterGui(@NotNull String title) { super(title); } + /** + * Constructs a new GUI + * + * @param title the title/name of this gui. + * @since 0.10.0 + */ + public StonecutterGui(@NotNull TextHolder title) { + super(title); + } + + /** + * Constructs a new stonecutter gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #StonecutterGui(String) + * @since 0.10.8 + */ + public StonecutterGui(@NotNull String title, @NotNull Plugin plugin) { + super(title, plugin); + } + + /** + * Constructs a new stonecutter gui for the given {@code plugin}. + * + * @param title the title/name of this gui. + * @param plugin the owning plugin of this gui + * @see #StonecutterGui(TextHolder) + * @since 0.10.8 + */ + public StonecutterGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(title, plugin); + } + @Override - public void show(@NotNull HumanEntity humanEntity) { - if (!(humanEntity instanceof Player)) { - throw new IllegalArgumentException("Enchanting tables can only be opened by players"); + public void update() { + super.updating = true; + + if (isDirty()) { + Inventory oldInventory = this.inventory; + this.inventory = createInventory(); + + if (oldInventory != null) { + for (HumanEntity viewer : new ArrayList<>(oldInventory.getViewers())) { + viewer.openInventory(this.inventory); + } + } + + markChanges(); } getInventory().clear(); - getHumanEntityCache().store(humanEntity); - getInputComponent().display(getInventory(), 0); getResultComponent().display(getInventory(), 1); getPlayerInventoryComponent().display(); + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + populateBottomInventory(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!super.updating) { + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + } + + super.updating = false; + } + + @Override + public void show(@NotNull HumanEntity humanEntity) { + if (super.inventory == null) { + update(); + } + + populateBottomInventory(humanEntity); + + humanEntity.openInventory(getInventory()); + } + + /** + * Populates the inventory of the {@link HumanEntity} if needed. + * + * @param humanEntity the human entity + * @since 0.11.4 + */ + private void populateBottomInventory(@NotNull HumanEntity humanEntity) { if (getPlayerInventoryComponent().hasItem()) { - humanEntity.getInventory().clear(); + HumanEntityCache humanEntityCache = getHumanEntityCache(); + + if (!humanEntityCache.contains(humanEntity)) { + humanEntityCache.storeAndClear(humanEntity); + } getPlayerInventoryComponent().placeItems(humanEntity.getInventory(), 0); - } else { - getHumanEntityCache().clearCache(humanEntity); } - - stonecutterInventory.openInventory((Player) humanEntity, getTitle(), getTopItems()); } @NotNull @Contract(pure = true) @Override public StonecutterGui copy() { - StonecutterGui gui = new StonecutterGui(getTitle()); + StonecutterGui gui = new StonecutterGui(getTitleHolder(), super.plugin); gui.inputComponent = inputComponent.copy(); gui.resultComponent = resultComponent.copy(); @@ -127,6 +209,16 @@ public void click(@NotNull InventoryClickEvent event) { } } + @NotNull + @Override + public Inventory getInventory() { + if (this.inventory == null) { + this.inventory = createInventory(); + } + + return inventory; + } + @Contract(pure = true) @Override public boolean isPlayerInventoryUsed() { @@ -136,29 +228,25 @@ public boolean isPlayerInventoryUsed() { @NotNull @Contract(pure = true) @Override - public Inventory createInventory(@NotNull String title) { - return Bukkit.createInventory(this, InventoryType.STONECUTTER, title); - } + public Inventory createInventory() { + Inventory inventory = this.stonecutterInventory.createInventory(getTitleHolder()); - /** - * Handles an incoming inventory click event - * - * @param event the event to handle - * @since 0.8.0 - */ - public void handleClickEvent(@NotNull InventoryClickEvent event) { - int slot = event.getRawSlot(); - Player player = (Player) event.getWhoClicked(); + addInventory(inventory, this); - if (slot >= 2 && slot <= 37) { - stonecutterInventory.sendItems(player, getTopItems()); - } else if (slot == 0 || slot == 1) { - stonecutterInventory.sendItems(player, getTopItems()); + return inventory; + } - if (event.isCancelled()) { - stonecutterInventory.clearCursor(player); - } - } + @Contract(pure = true) + @Override + public int getViewerCount() { + return getInventory().getViewers().size(); + } + + @NotNull + @Contract(pure = true) + @Override + public List getViewers() { + return new ArrayList<>(getInventory().getViewers()); } /** @@ -197,39 +285,27 @@ public InventoryComponent getPlayerInventoryComponent() { return playerInventoryComponent; } - /** - * Get the top items - * - * @return the top items - * @since 0.8.0 - */ - @Nullable - @Contract(pure = true) - private ItemStack[] getTopItems() { - return new ItemStack[] { - getInputComponent().getItem(0, 0), - getResultComponent().getItem(0, 0) - }; - } - /** * Loads a stone cutter gui from an XML file. * * @param instance the instance on which to reference fields and methods * @param inputStream the input stream containing the XML data + * @param plugin the plugin that will be the owner of the created gui * @return the loaded stone cutter gui - * @since 0.8.0 + * @see #load(Object, InputStream) + * @since 0.10.8 */ @Nullable @Contract(pure = true) - public static StonecutterGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + public static StonecutterGui load(@NotNull Object instance, @NotNull InputStream inputStream, + @NotNull Plugin plugin) { try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); Element documentElement = document.getDocumentElement(); documentElement.normalize(); - return load(instance, documentElement); + return load(instance, documentElement, plugin); } catch (SAXException | ParserConfigurationException | IOException e) { e.printStackTrace(); return null; @@ -241,16 +317,18 @@ public static StonecutterGui load(@NotNull Object instance, @NotNull InputStream * * @param instance the instance on which to reference fields and methods * @param element the element to load the gui from + * @param plugin the plugin that will be the owner of the created gui * @return the loaded stonecutter gui - * @since 0.8.0 + * @see #load(Object, Element) + * @since 0.10.8 */ @NotNull - public static StonecutterGui load(@NotNull Object instance, @NotNull Element element) { + public static StonecutterGui load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { if (!element.hasAttribute("title")) { throw new XMLLoadException("Provided XML element's gui tag doesn't have the mandatory title attribute set"); } - StonecutterGui stonecutterGui = new StonecutterGui(element.getAttribute("title")); + StonecutterGui stonecutterGui = new StonecutterGui(element.getAttribute("title"), plugin); stonecutterGui.initializeOrThrow(instance, element); if (element.hasAttribute("populate")) { @@ -292,9 +370,36 @@ public static StonecutterGui load(@NotNull Object instance, @NotNull Element ele throw new XMLLoadException("Unknown component name"); } - component.load(instance, componentElement); + component.load(instance, componentElement, plugin); } return stonecutterGui; } + + /** + * Loads a stone cutter gui from an XML file. + * + * @param instance the instance on which to reference fields and methods + * @param inputStream the input stream containing the XML data + * @return the loaded stone cutter gui + * @since 0.8.0 + */ + @Nullable + @Contract(pure = true) + public static StonecutterGui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(StonecutterGui.class)); + } + + /** + * Loads a stonecutter gui from the specified element, applying code references to the provided instance. + * + * @param instance the instance on which to reference fields and methods + * @param element the element to load the gui from + * @return the loaded stonecutter gui + * @since 0.8.0 + */ + @NotNull + public static StonecutterGui load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(StonecutterGui.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java index 028857291..45b652469 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/Gui.java @@ -1,677 +1,812 @@ -package com.github.stefvanschie.inventoryframework.gui.type.util; - -import com.github.stefvanschie.inventoryframework.HumanEntityCache; -import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; -import com.github.stefvanschie.inventoryframework.gui.GuiListener; -import com.github.stefvanschie.inventoryframework.gui.type.*; -import com.github.stefvanschie.inventoryframework.pane.*; -import com.github.stefvanschie.inventoryframework.pane.component.*; -import com.github.stefvanschie.inventoryframework.util.XMLUtil; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.bukkit.Bukkit; -import org.bukkit.entity.HumanEntity; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.inventory.InventoryEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The base class of all GUIs - */ -public abstract class Gui implements InventoryHolder { - - /** - * The inventory of this gui - */ - protected Inventory inventory; - - /** - * A player cache for storing player's inventories - */ - @NotNull - protected final HumanEntityCache humanEntityCache = new HumanEntityCache(); - - /** - * The consumer that will be called once a players clicks in the top-half of the gui - */ - @Nullable - protected Consumer onTopClick; - - /** - * The consumer that will be called once a players clicks in the bottom-half of the gui - */ - @Nullable - protected Consumer onBottomClick; - - /** - * The consumer that will be called once a players clicks in the gui or in their inventory - */ - @Nullable - protected Consumer onGlobalClick; - - /** - * The consumer that will be called once a player clicks outside of the gui screen - */ - @Nullable - protected Consumer onOutsideClick; - - /** - * The consumer that will be called once a player drags in the top-half of the gui - */ - @Nullable - protected Consumer onTopDrag; - - /** - * The consumer that will be called once a player drags in the bottom-half of the gui - */ - @Nullable - protected Consumer onBottomDrag; - - /** - * The consumer that will be called once a player drags in the gui or their inventory - */ - @Nullable - protected Consumer onGlobalDrag; - - /** - * The consumer that will be called once a player closes the gui - */ - @Nullable - protected Consumer onClose; - - /** - * Whether this gui is updating (as invoked by {@link #update()}), true if this is the case, false otherwise. This - * is used to indicate that inventory close events due to updating should be ignored. - */ - boolean updating = false; - - /** - * The pane mapping which will allow users to register their own panes to be used in XML files - */ - @NotNull - private static final Map> PANE_MAPPINGS = new HashMap<>(); - - /** - * The gui mappings which determine which gui type belongs to which identifier - */ - @NotNull - private static final Map> GUI_MAPPINGS - = new HashMap<>(); - - /** - * A map containing the relations between inventories and their respective gui. This is needed because Bukkit and - * Spigot ignore inventory holders for beacons, brewing stands, dispensers, droppers, furnaces and hoppers. The - * inventory holder for beacons is already being set properly via NMS, but this contains the other inventory types. - */ - @NotNull - private static final Map GUI_INVENTORIES = new WeakHashMap<>(); - - /** - * Whether listeners have ben registered by some gui - */ - private static boolean hasRegisteredListeners; - - /** - * Constructs a new GUI - * - * @since 0.8.0 - */ - public Gui() { - if (!hasRegisteredListeners) { - Bukkit.getPluginManager().registerEvents(new GuiListener(), - JavaPlugin.getProvidingPlugin(getClass())); - - hasRegisteredListeners = true; - } - } - - /** - * Creates a new inventory of the type of the implementing class with the provided title. - * - * @return the new inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public abstract Inventory createInventory(); - - /** - * Shows a gui to a player - * - * @param humanEntity the human entity to show the gui to - */ - public abstract void show(@NotNull HumanEntity humanEntity); - - /** - * Makes a copy of this gui and returns it. This makes a deep copy of the gui. This entails that the underlying - * panes will be copied as per their {@link Pane#copy} and miscellaneous data will be copied. The copy of this gui, - * will however have no viewers even if this gui currently has viewers. With this, cache data for viewers will also - * be non-existent for the copied gui. The returned gui will never be reference equal to the current gui. - * - * @return a copy of the gui - * @since 0.6.2 - */ - @NotNull - @Contract(pure = true) - public abstract Gui copy(); - - /** - * This should delegate the provided inventory click event to the right pane, which can then handle this click event - * further. This should not call any internal click handlers, since those will already have been activated. - * - * @param event the event to delegate - * @since 0.8.0 - */ - public abstract void click(@NotNull InventoryClickEvent event); - - /** - * Gets whether the player inventory is currently in use. This means whether the player inventory currently has an - * item in it. - * - * @return true if the player inventory is occupied, false otherwise - * @since 0.8.0 - */ - public abstract boolean isPlayerInventoryUsed(); - - /** - * Gets the count of {@link HumanEntity} instances that are currently viewing this GUI. - * - * @return the count of viewers - * @since 0.5.19 - */ - @Contract(pure = true) - public int getViewerCount() { - return getInventory().getViewers().size(); - } - - /** - * Gets a mutable snapshot of the current {@link HumanEntity} viewers of this GUI. - * This is a snapshot (copy) and not a view, therefore modifications aren't visible. - * - * @return a snapshot of the current viewers - * @see #getViewerCount() - * @since 0.5.19 - */ - @NotNull - @Contract(pure = true) - public List getViewers() { - return new ArrayList<>(getInventory().getViewers()); - } - - /** - * Update the gui for everyone - */ - public void update() { - updating = true; - - getViewers().forEach(this::show); - - if (!updating) - throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); - - updating = false; - } - - /** - * Adds the specified inventory and gui, so we can properly intercept clicks. - * - * @param inventory the inventory for the specified gui - * @param gui the gui belonging to the specified inventory - * @since 0.8.1 - */ - protected void addInventory(@NotNull Inventory inventory, @NotNull Gui gui) { - GUI_INVENTORIES.put(inventory, gui); - } - - /** - * Gets a gui from the specified inventory. Only guis of type beacon, brewing stand, dispenser, dropper, furnace and - * hopper can be retrieved. - * - * @param inventory the inventory to get the gui from - * @return the gui or null if the inventory doesn't have an accompanying gui - * @since 0.8.1 - */ - @Nullable - @Contract(pure = true) - public static Gui getGui(@NotNull Inventory inventory) { - return GUI_INVENTORIES.get(inventory); - } - - /** - * Gets the human entity cache used for this gui - * - * @return the human entity cache - * @see HumanEntityCache - * @since 0.5.4 - */ - @NotNull - @Contract(pure = true) - public HumanEntityCache getHumanEntityCache() { - return humanEntityCache; - } - - /** - * Loads a Gui from a given input stream. - * Returns null instead of throwing an exception in case of a failure. - * - * @param instance the class instance for all reflection lookups - * @param inputStream the file - * @return the gui or null if the loading failed - */ - @Nullable - public static Gui load(@NotNull Object instance, @NotNull InputStream inputStream) { - try { - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); - Element documentElement = document.getDocumentElement(); - - documentElement.normalize(); - - if (!documentElement.hasAttribute("type")) { - throw new XMLLoadException("Type attribute must be specified when loading via Gui.load"); - } - - return GUI_MAPPINGS.get(documentElement.getAttribute("type")).apply(instance, documentElement); - } catch (SAXException | ParserConfigurationException | IOException e) { - e.printStackTrace(); - return null; - } - } - - /** - * Initializes standard fields from a Gui from a given input stream. - * Throws a {@link RuntimeException} instead of returning null in case of a failure. - * - * @param instance the class instance for all reflection lookups - * @param element the gui element - * @see #load(Object, InputStream) - */ - protected void initializeOrThrow(@NotNull Object instance, @NotNull Element element) { - if (element.hasAttribute("field")) - XMLUtil.loadFieldAttribute(instance, element, this); - - if (element.hasAttribute("onTopClick")) { - setOnTopClick(XMLUtil.loadOnEventAttribute(instance, - element, InventoryClickEvent.class, "onTopClick")); - } - - if (element.hasAttribute("onBottomClick")) { - setOnBottomClick(XMLUtil.loadOnEventAttribute(instance, - element, InventoryClickEvent.class, "onBottomClick")); - } - - if (element.hasAttribute("onGlobalClick")) { - setOnGlobalClick(XMLUtil.loadOnEventAttribute(instance, - element, InventoryClickEvent.class, "onGlobalClick")); - } - - if (element.hasAttribute("onOutsideClick")) { - setOnOutsideClick(XMLUtil.loadOnEventAttribute(instance, - element, InventoryClickEvent.class, "onOutsideClick")); - } - - if (element.hasAttribute("onTopDrag")) { - setOnTopDrag(XMLUtil.loadOnEventAttribute(instance, - element, InventoryDragEvent.class, "onTopDrag")); - } - - if (element.hasAttribute("onBottomDrag")) { - setOnBottomDrag(XMLUtil.loadOnEventAttribute(instance, - element, InventoryDragEvent.class, "onBottomDrag")); - } - - if (element.hasAttribute("onGlobalDrag")) { - setOnGlobalDrag(XMLUtil.loadOnEventAttribute(instance, - element, InventoryDragEvent.class, "onGlobalDrag")); - } - - if (element.hasAttribute("onClose")) { - setOnClose(XMLUtil.loadOnEventAttribute(instance, - element, InventoryCloseEvent.class, "onClose")); - } - - if (element.hasAttribute("populate")) { - try { - MethodUtils.invokeExactMethod(instance, "populate", this, Gui.class); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - Plugin plugin = JavaPlugin.getProvidingPlugin(Gui.class); - - throw new XMLLoadException("Error loading " + plugin.getName() + "'s gui with associated class: " - + instance.getClass().getSimpleName(), e); - } - } - } - - /** - * Set the consumer that should be called whenever this gui is clicked in. - * - * @param onTopClick the consumer that gets called - */ - public void setOnTopClick(@Nullable Consumer onTopClick) { - this.onTopClick = onTopClick; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnTopClick(Consumer)}, - * so the consumer that should be called whenever this gui is clicked in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnTopClick(@NotNull InventoryClickEvent event) { - callCallback(onTopClick, event, "onTopClick"); - } - - /** - * Set the consumer that should be called whenever the inventory is clicked in. - * - * @param onBottomClick the consumer that gets called - */ - public void setOnBottomClick(@Nullable Consumer onBottomClick) { - this.onBottomClick = onBottomClick; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnBottomClick(Consumer)}, - * so the consumer that should be called whenever the inventory is clicked in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnBottomClick(@NotNull InventoryClickEvent event) { - callCallback(onBottomClick, event, "onBottomClick"); - } - - /** - * Set the consumer that should be called whenever this gui or inventory is clicked in. - * - * @param onGlobalClick the consumer that gets called - */ - public void setOnGlobalClick(@Nullable Consumer onGlobalClick) { - this.onGlobalClick = onGlobalClick; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnGlobalClick(Consumer)}, - * so the consumer that should be called whenever this gui or inventory is clicked in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnGlobalClick(@NotNull InventoryClickEvent event) { - callCallback(onGlobalClick, event, "onGlobalClick"); - } - - /** - * Set the consumer that should be called whenever a player clicks outside the gui. - * - * @param onOutsideClick the consumer that gets called - * @since 0.5.7 - */ - public void setOnOutsideClick(@Nullable Consumer onOutsideClick) { - this.onOutsideClick = onOutsideClick; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnOutsideClick(Consumer)}, - * so the consumer that should be called whenever a player clicks outside the gui. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnOutsideClick(@NotNull InventoryClickEvent event) { - callCallback(onOutsideClick, event, "onOutsideClick"); - } - - /** - * Set the consumer that should be called whenever this gui's top half is dragged in. - * - * @param onTopDrag the consumer that gets called - * @since 0.9.0 - */ - public void setOnTopDrag(@Nullable Consumer onTopDrag) { - this.onTopDrag = onTopDrag; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnTopDrag(Consumer)}, - * so the consumer that should be called whenever this gui's top half is dragged in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.9.0 - */ - public void callOnTopDrag(@NotNull InventoryDragEvent event) { - callCallback(onTopDrag, event, "onTopDrag"); - } - - /** - * Set the consumer that should be called whenever the inventory is dragged in. - * - * @param onBottomDrag the consumer that gets called - * @since 0.9.0 - */ - public void setOnBottomDrag(@Nullable Consumer onBottomDrag) { - this.onBottomDrag = onBottomDrag; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnBottomDrag(Consumer)}, - * so the consumer that should be called whenever the inventory is dragged in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.9.0 - */ - public void callOnBottomDrag(@NotNull InventoryDragEvent event) { - callCallback(onBottomDrag, event, "onBottomDrag"); - } - - /** - * Set the consumer that should be called whenever this gui or inventory is dragged in. - * - * @param onGlobalDrag the consumer that gets called - * @since 0.9.0 - */ - public void setOnGlobalDrag(@Nullable Consumer onGlobalDrag) { - this.onGlobalDrag = onGlobalDrag; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnGlobalDrag(Consumer)}, - * so the consumer that should be called whenever this gui or inventory is dragged in. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnGlobalDrag(@NotNull InventoryDragEvent event) { - callCallback(onGlobalDrag, event, "onGlobalDrag"); - } - - /** - * Set the consumer that should be called whenever this gui is closed. - * - * @param onClose the consumer that gets called - */ - public void setOnClose(@Nullable Consumer onClose) { - this.onClose = onClose; - } - - /** - * Calls the consumer (if it's not null) that was specified using {@link #setOnClose(Consumer)}, - * so the consumer that should be called whenever this gui is closed. - * Catches and logs all exceptions the consumer might throw. - * - * @param event the event to handle - * @since 0.6.0 - */ - public void callOnClose(@NotNull InventoryCloseEvent event) { - callCallback(onClose, event, "onClose"); - } - - /** - * Calls the specified consumer (if it's not null) with the specified parameter, - * catching and logging all exceptions it might throw. - * - * @param callback the consumer to call if it isn't null - * @param event the value the consumer should accept - * @param callbackName the name of the action, used for logging - * @param the type of the value the consumer is accepting - */ - private void callCallback(@Nullable Consumer callback, - @NotNull T event, @NotNull String callbackName) { - if (callback == null) { - return; - } - - try { - callback.accept(event); - } catch (Throwable t) { - Logger logger = JavaPlugin.getProvidingPlugin(getClass()).getLogger(); - String message = "Exception while handling " + callbackName; - if (event instanceof InventoryClickEvent) { - InventoryClickEvent clickEvent = (InventoryClickEvent) event; - message += ", slot=" + clickEvent.getSlot(); - } - logger.log(Level.SEVERE, message, t); - } - } - - @NotNull - @Override - public Inventory getInventory() { - if (this.inventory == null) { - this.inventory = createInventory(); - } - - return inventory; - } - - /** - * Gets whether this gui is being updated, as invoked by {@link #update()}. This returns true if this is the case - * and false otherwise. - * - * @return whether this gui is being updated - * @since 0.5.15 - */ - @Contract(pure = true) - public boolean isUpdating() { - return updating; - } - - /** - * Registers a property that can be used inside an XML file to add additional new properties. - * - * @param attributeName the name of the property. This is the same name you'll be using to specify the property - * type in the XML file. - * @param function how the property should be processed. This converts the raw text input from the XML node value - * into the correct object type. - * @throws IllegalArgumentException when a property with this name is already registered. - */ - public static void registerProperty(@NotNull String attributeName, @NotNull Function function) { - Pane.registerProperty(attributeName, function); - } - - /** - * Registers a name that can be used inside an XML file to add custom panes - * - * @param name the name of the pane to be used in the XML file - * @param biFunction how the pane loading should be processed - * @throws IllegalArgumentException when a pane with this name is already registered - */ - public static void registerPane(@NotNull String name, @NotNull BiFunction biFunction) { - if (PANE_MAPPINGS.containsKey(name)) { - throw new IllegalArgumentException("pane name '" + name + "' is already registered"); - } - - PANE_MAPPINGS.put(name, biFunction); - } - - /** - * Registers a type that can be used inside an XML file to specify the gui type - * - * @param name the name of the type of gui to be used in an XML file - * @param biFunction how the gui creation should be processed - * @throws IllegalArgumentException when a gui type with this name is already registered - */ - public static void registerGui(@NotNull String name, @NotNull BiFunction biFunction) { - if (GUI_MAPPINGS.containsKey(name)) { - throw new IllegalArgumentException("Gui name '" + name + "' is already registered"); - } - - GUI_MAPPINGS.put(name, biFunction); - } - - /** - * Loads a pane by the given instance and node - * - * @param instance the instance - * @param node the node - * @return the pane - */ - @NotNull - public static Pane loadPane(@NotNull Object instance, @NotNull Node node) { - return PANE_MAPPINGS.get(node.getNodeName()).apply(instance, (Element) node); - } - - static { - registerPane("masonrypane", MasonryPane::load); - registerPane("outlinepane", OutlinePane::load); - registerPane("paginatedpane", PaginatedPane::load); - registerPane("patternpane", PatternPane::load); - registerPane("staticpane", StaticPane::load); - - registerPane("cyclebutton", CycleButton::load); - registerPane("label", Label::load); - registerPane("percentagebar", PercentageBar::load); - registerPane("slider", Slider::load); - registerPane("togglebutton", ToggleButton::load); - - registerGui("anvil", AnvilGui::load); - registerGui("barrel", BarrelGui::load); - registerGui("beacon", BeaconGui::load); - registerGui("blast-furnace", BlastFurnaceGui::load); - registerGui("brewing-stand", BrewingStandGui::load); - registerGui("cartography-table", CartographyTableGui::load); - registerGui("chest", ChestGui::load); - registerGui("crafting-table", CraftingTableGui::load); - registerGui("dispenser", DispenserGui::load); - registerGui("dropper", DropperGui::load); - registerGui("enchanting-table", EnchantingTableGui::load); - registerGui("ender-chest", EnderChestGui::load); - registerGui("furnace", FurnaceGui::load); - registerGui("grindstone", GrindstoneGui::load); - registerGui("hopper", HopperGui::load); - registerGui("shulker-box", ShulkerBoxGui::load); - registerGui("smithing-table", SmithingTableGui::load); - registerGui("smoker", SmokerGui::load); - registerGui("stonecutter", StonecutterGui::load); - } -} +package com.github.stefvanschie.inventoryframework.gui.type.util; + +import com.github.stefvanschie.inventoryframework.HumanEntityCache; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.GuiListener; +import com.github.stefvanschie.inventoryframework.gui.type.*; +import com.github.stefvanschie.inventoryframework.pane.*; +import com.github.stefvanschie.inventoryframework.pane.component.*; +import com.github.stefvanschie.inventoryframework.util.TriFunction; +import com.github.stefvanschie.inventoryframework.util.XMLUtil; +import com.github.stefvanschie.inventoryframework.util.version.Version; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; + +/** + * The base class of all GUIs + */ +public abstract class Gui { + + /** + * The plugin that owns this gui + */ + @NotNull + protected final Plugin plugin; + + /** + * The inventory of this gui + */ + protected Inventory inventory; + + /** + * A player cache for storing player's inventories + */ + @NotNull + protected final HumanEntityCache humanEntityCache = new HumanEntityCache(); + + /** + * The consumer that will be called once a players clicks in the top-half of the gui + */ + @Nullable + protected Consumer onTopClick; + + /** + * The consumer that will be called once a players clicks in the bottom-half of the gui + */ + @Nullable + protected Consumer onBottomClick; + + /** + * The consumer that will be called once a players clicks in the gui or in their inventory + */ + @Nullable + protected Consumer onGlobalClick; + + /** + * The consumer that will be called once a player clicks outside of the gui screen + */ + @Nullable + protected Consumer onOutsideClick; + + /** + * The consumer that will be called once a player drags in the top-half of the gui + */ + @Nullable + protected Consumer onTopDrag; + + /** + * The consumer that will be called once a player drags in the bottom-half of the gui + */ + @Nullable + protected Consumer onBottomDrag; + + /** + * The consumer that will be called once a player drags in the gui or their inventory + */ + @Nullable + protected Consumer onGlobalDrag; + + /** + * The consumer that will be called once a player closes the gui + */ + @Nullable + protected Consumer onClose; + + /** + * Whether this gui is updating (as invoked by {@link #update()}), true if this is the case, false otherwise. This + * is used to indicate that inventory close events due to updating should be ignored. + */ + protected boolean updating = false; + + /** + * The parent gui. This gui will be navigated to once a player closes this gui. If this is null, the player will not + * be redirected to another gui once they close this gui. + */ + @Nullable + private Gui parent; + + /** + * The pane mapping which will allow users to register their own panes to be used in XML files + */ + @NotNull + private static final Map> + PANE_MAPPINGS = new HashMap<>(); + + /** + * The gui mappings which determine which gui type belongs to which identifier + */ + @NotNull + private static final Map> + GUI_MAPPINGS = new HashMap<>(); + + /** + * A map containing the relations between inventories and their respective gui. This is needed because Bukkit and + * Spigot ignore inventory holders for beacons, brewing stands, dispensers, droppers, furnaces and hoppers. The + * inventory holder for beacons is already being set properly via NMS, but this contains the other inventory types. + */ + @NotNull + private static final Map GUI_INVENTORIES = new WeakHashMap<>(); + + /** + * Whether listeners have ben registered by some gui + */ + private static boolean hasRegisteredListeners; + + /** + * Constructs a new gui with the provided plugin. + * + * @param plugin the plugin + * @since 0.10.8 + */ + public Gui(@NotNull Plugin plugin) { + this.plugin = plugin; + + if (!hasRegisteredListeners) { + /*This throws an exception if the version is unsupported. We want this thrown if our version is unsupported, + to prevent people opening guis that do not behave correctly. */ + //noinspection ResultOfMethodCallIgnored + Version.getVersion(); + + Bukkit.getPluginManager().registerEvents(new GuiListener(plugin), plugin); + + hasRegisteredListeners = true; + } + } + + /** + * Shows a gui to a player + * + * @param humanEntity the human entity to show the gui to + */ + public abstract void show(@NotNull HumanEntity humanEntity); + + /** + * Makes a copy of this gui and returns it. This makes a deep copy of the gui. This entails that the underlying + * panes will be copied as per their {@link Pane#copy} and miscellaneous data will be copied. The copy of this gui, + * will however have no viewers even if this gui currently has viewers. With this, cache data for viewers will also + * be non-existent for the copied gui. The original owning plugin of the gui is preserved, but the plugin will not + * be deeply copied. The returned gui will never be reference equal to the current gui. + * + * @return a copy of the gui + * @since 0.6.2 + */ + @NotNull + @Contract(pure = true) + public abstract Gui copy(); + + /** + * This should delegate the provided inventory click event to the right pane, which can then handle this click event + * further. This should not call any internal click handlers, since those will already have been activated. + * + * @param event the event to delegate + * @since 0.8.0 + */ + public abstract void click(@NotNull InventoryClickEvent event); + + /** + * Gets whether the player inventory is currently in use. This means whether the player inventory currently has an + * item in it. + * + * @return true if the player inventory is occupied, false otherwise + * @since 0.8.0 + */ + public abstract boolean isPlayerInventoryUsed(); + + /** + * Gets the count of {@link HumanEntity} instances that are currently viewing this GUI. + * + * @return the count of viewers + * @since 0.5.19 + */ + @Contract(pure = true) + public abstract int getViewerCount(); + + /** + * Gets a mutable snapshot of the current {@link HumanEntity} viewers of this GUI. + * This is a snapshot (copy) and not a view, therefore modifications aren't visible. + * + * @return a snapshot of the current viewers + * @see #getViewerCount() + * @since 0.5.19 + */ + @NotNull + @Contract(pure = true) + public abstract List getViewers(); + + //TODO: next breaking version, make this method abstract, all implementers override this + /** + * Update the gui for everyone + */ + public void update() { + updating = true; + + for (HumanEntity viewer : getViewers()) { + ItemStack cursor = viewer.getItemOnCursor(); + viewer.setItemOnCursor(new ItemStack(Material.AIR)); + + show(viewer); + + viewer.setItemOnCursor(cursor); + } + + if (!updating) + throw new AssertionError("Gui#isUpdating became false before Gui#update finished"); + + updating = false; + } + + /** + * Adds the specified inventory and gui, so we can properly intercept clicks. + * + * @param inventory the inventory for the specified gui + * @param gui the gui belonging to the specified inventory + * @since 0.8.1 + */ + protected void addInventory(@NotNull Inventory inventory, @NotNull Gui gui) { + GUI_INVENTORIES.put(inventory, gui); + } + + /** + * Gets a gui from the specified inventory. Only guis of type beacon, brewing stand, dispenser, dropper, furnace and + * hopper can be retrieved. + * + * @param inventory the inventory to get the gui from + * @return the gui or null if the inventory doesn't have an accompanying gui + * @since 0.8.1 + */ + @Nullable + @Contract(pure = true) + public static Gui getGui(@NotNull Inventory inventory) { + return GUI_INVENTORIES.get(inventory); + } + + /** + * Gets the human entity cache used for this gui + * + * @return the human entity cache + * @see HumanEntityCache + * @since 0.5.4 + */ + @NotNull + @Contract(pure = true) + public HumanEntityCache getHumanEntityCache() { + return humanEntityCache; + } + + /** + * Loads a Gui from a given input stream. + * + * @param instance the class instance for all reflection lookups + * @param inputStream the file + * @return the gui or null if the loading failed + * @throws XMLLoadException if loading could not finish successfully, due to e.g., a malformed file + * @see #load(Object, InputStream) + * @since 0.10.8 + */ + @Nullable + public static Gui load(@NotNull Object instance, @NotNull InputStream inputStream, @NotNull Plugin plugin) { + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); + Element documentElement = document.getDocumentElement(); + + documentElement.normalize(); + + if (!documentElement.hasAttribute("type")) { + throw new XMLLoadException("Type attribute must be specified when loading via Gui.load"); + } + + String type = documentElement.getAttribute("type"); + TriFunction mapping = GUI_MAPPINGS + .get(type); + + if (mapping == null) { + throw new XMLLoadException("Type attribute '" + type + "' is invalid"); + } + + return mapping.apply(instance, documentElement, plugin); + } catch (SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Loads a Gui from a given input stream. + * Returns null instead of throwing an exception in case of a failure. + * + * @param instance the class instance for all reflection lookups + * @param inputStream the file + * @return the gui or null if the loading failed + * @throws XMLLoadException if loading could not finish successfully, due to e.g., a malformed file + */ + @Nullable + public static Gui load(@NotNull Object instance, @NotNull InputStream inputStream) { + return load(instance, inputStream, JavaPlugin.getProvidingPlugin(Gui.class)); + } + + /** + * Initializes standard fields from a Gui from a given input stream. + * Throws a {@link RuntimeException} instead of returning null in case of a failure. + * + * @param instance the class instance for all reflection lookups + * @param element the gui element + * @see #load(Object, InputStream) + */ + protected void initializeOrThrow(@NotNull Object instance, @NotNull Element element) { + if (element.hasAttribute("field")) + XMLUtil.loadFieldAttribute(instance, element, this); + + if (element.hasAttribute("onTopClick")) { + setOnTopClick(XMLUtil.loadOnEventAttribute(instance, + element, InventoryClickEvent.class, "onTopClick")); + } + + if (element.hasAttribute("onBottomClick")) { + setOnBottomClick(XMLUtil.loadOnEventAttribute(instance, + element, InventoryClickEvent.class, "onBottomClick")); + } + + if (element.hasAttribute("onGlobalClick")) { + setOnGlobalClick(XMLUtil.loadOnEventAttribute(instance, + element, InventoryClickEvent.class, "onGlobalClick")); + } + + if (element.hasAttribute("onOutsideClick")) { + setOnOutsideClick(XMLUtil.loadOnEventAttribute(instance, + element, InventoryClickEvent.class, "onOutsideClick")); + } + + if (element.hasAttribute("onTopDrag")) { + setOnTopDrag(XMLUtil.loadOnEventAttribute(instance, + element, InventoryDragEvent.class, "onTopDrag")); + } + + if (element.hasAttribute("onBottomDrag")) { + setOnBottomDrag(XMLUtil.loadOnEventAttribute(instance, + element, InventoryDragEvent.class, "onBottomDrag")); + } + + if (element.hasAttribute("onGlobalDrag")) { + setOnGlobalDrag(XMLUtil.loadOnEventAttribute(instance, + element, InventoryDragEvent.class, "onGlobalDrag")); + } + + if (element.hasAttribute("onClose")) { + setOnClose(XMLUtil.loadOnEventAttribute(instance, + element, InventoryCloseEvent.class, "onClose")); + } + + if (element.hasAttribute("populate")) { + XMLUtil.invokeMethod(instance, element.getAttribute("populate"), this, Gui.class); + } + } + + /** + * Set the consumer that should be called whenever this gui is clicked in. + * + * @param onTopClick the consumer that gets called + */ + public void setOnTopClick(@Nullable Consumer onTopClick) { + this.onTopClick = onTopClick; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnTopClick(Consumer)}, + * so the consumer that should be called whenever this gui is clicked in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnTopClick(@NotNull InventoryClickEvent event) { + callCallback(onTopClick, event, "onTopClick"); + } + + /** + * Set the consumer that should be called whenever the inventory is clicked in. + * + * @param onBottomClick the consumer that gets called + */ + public void setOnBottomClick(@Nullable Consumer onBottomClick) { + this.onBottomClick = onBottomClick; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnBottomClick(Consumer)}, + * so the consumer that should be called whenever the inventory is clicked in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnBottomClick(@NotNull InventoryClickEvent event) { + callCallback(onBottomClick, event, "onBottomClick"); + } + + /** + * Set the consumer that should be called whenever this gui or inventory is clicked in. + * + * @param onGlobalClick the consumer that gets called + */ + public void setOnGlobalClick(@Nullable Consumer onGlobalClick) { + this.onGlobalClick = onGlobalClick; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnGlobalClick(Consumer)}, + * so the consumer that should be called whenever this gui or inventory is clicked in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnGlobalClick(@NotNull InventoryClickEvent event) { + callCallback(onGlobalClick, event, "onGlobalClick"); + } + + /** + * Set the consumer that should be called whenever a player clicks outside the gui. + * + * @param onOutsideClick the consumer that gets called + * @since 0.5.7 + */ + public void setOnOutsideClick(@Nullable Consumer onOutsideClick) { + this.onOutsideClick = onOutsideClick; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnOutsideClick(Consumer)}, + * so the consumer that should be called whenever a player clicks outside the gui. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnOutsideClick(@NotNull InventoryClickEvent event) { + callCallback(onOutsideClick, event, "onOutsideClick"); + } + + /** + * Set the consumer that should be called whenever this gui's top half is dragged in. + * + * @param onTopDrag the consumer that gets called + * @since 0.9.0 + */ + public void setOnTopDrag(@Nullable Consumer onTopDrag) { + this.onTopDrag = onTopDrag; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnTopDrag(Consumer)}, + * so the consumer that should be called whenever this gui's top half is dragged in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.9.0 + */ + public void callOnTopDrag(@NotNull InventoryDragEvent event) { + callCallback(onTopDrag, event, "onTopDrag"); + } + + /** + * Set the consumer that should be called whenever the inventory is dragged in. + * + * @param onBottomDrag the consumer that gets called + * @since 0.9.0 + */ + public void setOnBottomDrag(@Nullable Consumer onBottomDrag) { + this.onBottomDrag = onBottomDrag; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnBottomDrag(Consumer)}, + * so the consumer that should be called whenever the inventory is dragged in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.9.0 + */ + public void callOnBottomDrag(@NotNull InventoryDragEvent event) { + callCallback(onBottomDrag, event, "onBottomDrag"); + } + + /** + * Set the consumer that should be called whenever this gui or inventory is dragged in. + * + * @param onGlobalDrag the consumer that gets called + * @since 0.9.0 + */ + public void setOnGlobalDrag(@Nullable Consumer onGlobalDrag) { + this.onGlobalDrag = onGlobalDrag; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnGlobalDrag(Consumer)}, + * so the consumer that should be called whenever this gui or inventory is dragged in. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnGlobalDrag(@NotNull InventoryDragEvent event) { + callCallback(onGlobalDrag, event, "onGlobalDrag"); + } + + /** + * Set the consumer that should be called whenever this gui is closed. + * + * @param onClose the consumer that gets called + */ + public void setOnClose(@Nullable Consumer onClose) { + this.onClose = onClose; + } + + /** + * Calls the consumer (if it's not null) that was specified using {@link #setOnClose(Consumer)}, + * so the consumer that should be called whenever this gui is closed. + * Catches and logs all exceptions the consumer might throw. + * + * @param event the event to handle + * @since 0.6.0 + */ + public void callOnClose(@NotNull InventoryCloseEvent event) { + callCallback(onClose, event, "onClose"); + } + + /** + * Calls the specified consumer (if it's not null) with the specified parameter, + * catching and logging all exceptions it might throw. + * + * @param callback the consumer to call if it isn't null + * @param event the value the consumer should accept + * @param callbackName the name of the action, used for logging + * @param the type of the value the consumer is accepting + */ + protected void callCallback(@Nullable Consumer callback, + @NotNull T event, @NotNull String callbackName) { + if (callback == null) { + return; + } + + try { + callback.accept(event); + } catch (Throwable t) { + String message = "Exception while handling " + callbackName; + if (event instanceof InventoryClickEvent) { + InventoryClickEvent clickEvent = (InventoryClickEvent) event; + message += ", slot=" + clickEvent.getSlot(); + } + + this.plugin.getLogger().log(Level.SEVERE, message, t); + } + } + + /** + * The parent gui will be shown to the specified {@link HumanEntity}. If no parent gui is set, then this method will + * silently do nothing. + * + * @param humanEntity the human entity to redirect + * @since 0.10.14 + */ + public void navigateToParent(@NotNull HumanEntity humanEntity) { + if (this.parent == null) { + return; + } + + this.parent.show(humanEntity); + } + + /** + * Sets the parent gui to the provided gui. This is the gui that a player will be navigated to once they close this + * gui. The navigation will occur after the close event handler, set by {@link #setOnClose(Consumer)}, is called. If + * there was already a previous parent set, the provided gui will override the previous one. + * + * @param gui the new parent gui + * @since 0.10.14 + */ + public void setParent(@NotNull Gui gui) { + this.parent = gui; + } + + /** + * Gets whether this gui is being updated, as invoked by {@link #update()}. This returns true if this is the case + * and false otherwise. + * + * @return whether this gui is being updated + * @since 0.5.15 + */ + @Contract(pure = true) + public boolean isUpdating() { + return updating; + } + + /** + * Registers a property that can be used inside an XML file to add additional new properties. + * + * @param attributeName the name of the property. This is the same name you'll be using to specify the property + * type in the XML file. + * @param function how the property should be processed. This converts the raw text input from the XML node value + * into the correct object type. + * @throws IllegalArgumentException when a property with this name is already registered. + */ + public static void registerProperty(@NotNull String attributeName, @NotNull Function function) { + Pane.registerProperty(attributeName, function); + } + + /** + * Registers a name that can be used inside an XML file to add custom panes + * + * @param name the name of the pane to be used in the XML file + * @param triFunction how the pane loading should be processed + * @throws IllegalArgumentException when a pane with this name is already registered + * @see #registerPane(String, BiFunction) + * @since 0.10.8 + */ + public static void registerPane(@NotNull String name, + @NotNull TriFunction triFunction) { + if (PANE_MAPPINGS.containsKey(name)) { + throw new IllegalArgumentException("pane name '" + name + "' is already registered"); + } + + PANE_MAPPINGS.put(name, triFunction); + } + + /** + * Registers a name that can be used inside an XML file to add custom panes + * + * @param name the name of the pane to be used in the XML file + * @param biFunction how the pane loading should be processed + * @throws IllegalArgumentException when a pane with this name is already registered + */ + public static void registerPane(@NotNull String name, @NotNull BiFunction biFunction) { + registerPane(name, (object, element, plugin) -> biFunction.apply(object, element)); + } + + /** + * Registers a type that can be used inside an XML file to specify the gui type + * + * @param name the name of the type of gui to be used in an XML file + * @param triFunction how the gui creation should be processed + * @throws IllegalArgumentException when a gui type with this name is already registered + * @since 0.10.8 + */ + public static void registerGui(@NotNull String name, + @NotNull TriFunction triFunction) { + if (GUI_MAPPINGS.containsKey(name)) { + throw new IllegalArgumentException("Gui name '" + name + "' is already registered"); + } + + GUI_MAPPINGS.put(name, triFunction); + } + + /** + * Registers a type that can be used inside an XML file to specify the gui type + * + * @param name the name of the type of gui to be used in an XML file + * @param biFunction how the gui creation should be processed + * @throws IllegalArgumentException when a gui type with this name is already registered + * @deprecated this method is no longer used internally and has been superseded by + * {@link #registerPane(String, TriFunction)} + */ + @Deprecated + public static void registerGui(@NotNull String name, + @NotNull BiFunction biFunction) { + registerGui(name, (object, element, plugin) -> biFunction.apply(object, element)); + } + + /** + * Loads a pane by the given instance and node + * + * @param instance the instance + * @param node the node + * @param plugin the plugin to load the pane with + * @return the pane + * @throws XMLLoadException if the name of the node does not correspond to a valid pane. + * @since 0.10.8 + */ + @NotNull + public static Pane loadPane(@NotNull Object instance, @NotNull Node node, @NotNull Plugin plugin) { + String name = node.getNodeName(); + TriFunction mapping = PANE_MAPPINGS.get(name); + + if (mapping == null) { + throw new XMLLoadException("Pane '" + name + "' is not registered or does not exist"); + } + + return mapping.apply(instance, (Element) node, plugin); + } + + /** + * Loads a pane by the given instance and node + * + * @param instance the instance + * @param node the node + * @return the pane + */ + @NotNull + public static Pane loadPane(@NotNull Object instance, @NotNull Node node) { + return loadPane(instance, node, JavaPlugin.getProvidingPlugin(Gui.class)); + } + + static { + registerPane("masonrypane", + (TriFunction) MasonryPane::load); + registerPane("outlinepane", + (TriFunction) OutlinePane::load); + registerPane("paginatedpane", + (TriFunction) PaginatedPane::load); + registerPane("patternpane", + (TriFunction) PatternPane::load); + registerPane("staticpane", + (TriFunction) StaticPane::load); + + registerPane("cyclebutton", + (TriFunction) CycleButton::load); + registerPane("label", + (TriFunction) Label::load); + registerPane("pagingbuttons", PagingButtons::load); + registerPane("percentagebar", + (TriFunction) PercentageBar::load); + registerPane("slider", + (TriFunction) Slider::load); + registerPane("togglebutton", + (TriFunction) ToggleButton::load); + + registerGui("anvil", + (TriFunction) AnvilGui::load); + registerGui("barrel", + (TriFunction) BarrelGui::load); + registerGui("beacon", + (TriFunction) BeaconGui::load); + registerGui("blast-furnace", + (TriFunction) BlastFurnaceGui::load); + registerGui("brewing-stand", + (TriFunction) BrewingStandGui::load); + registerGui("cartography-table", + (TriFunction) CartographyTableGui::load); + registerGui("chest", + (TriFunction) ChestGui::load); + registerGui("crafting-table", + (TriFunction) CraftingTableGui::load); + registerGui("dispenser", + (TriFunction) DispenserGui::load); + registerGui("dropper", + (TriFunction) DropperGui::load); + registerGui("enchanting-table", + (TriFunction) EnchantingTableGui::load); + registerGui("ender-chest", + (TriFunction) EnderChestGui::load); + registerGui("furnace", + (TriFunction) FurnaceGui::load); + registerGui("grindstone", + (TriFunction) GrindstoneGui::load); + registerGui("hopper", + (TriFunction) HopperGui::load); + registerGui("merchant", + (TriFunction) MerchantGui::load); + registerGui("shulker-box", + (TriFunction) ShulkerBoxGui::load); + registerGui("smithing-table", + (TriFunction) SmithingTableGui::load); + registerGui("smoker", + (TriFunction) SmokerGui::load); + registerGui("stonecutter", + (TriFunction) StonecutterGui::load); + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/InventoryBased.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/InventoryBased.java new file mode 100644 index 000000000..8aaa21c29 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/InventoryBased.java @@ -0,0 +1,20 @@ +package com.github.stefvanschie.inventoryframework.gui.type.util; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public interface InventoryBased extends InventoryHolder { + + /** + * Creates a new inventory of the type of the implementing class. + * + * @return the new inventory + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + Inventory createInventory(); + +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/NamedGui.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/NamedGui.java index 1ac7127f5..2bba5b869 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/NamedGui.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/gui/type/util/NamedGui.java @@ -1,21 +1,24 @@ package com.github.stefvanschie.inventoryframework.gui.type.util; -import org.bukkit.Bukkit; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.Inventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import java.util.List; - public abstract class NamedGui extends Gui { /** * The title of this gui */ @NotNull - private String title; + private TextHolder title; + + /** + * Whether the title is dirty i.e., has changed + */ + private boolean dirty = false; /** * Constructs a new gui with a title @@ -24,60 +27,67 @@ public abstract class NamedGui extends Gui { * @since 0.8.0 */ public NamedGui(@NotNull String title) { - this.title = title; + this(StringHolder.of(title)); } /** - * Creates a new inventory of the type of the implementing class with the provided title. + * Constructs a new gui with a title * - * @param title the title for the new inventory - * @return the new inventory - * @since 0.8.0 + * @param title the title/name of this gui + * @since 0.10.0 */ - @NotNull - @Contract(pure = true) - public abstract Inventory createInventory(@NotNull String title); - - @NotNull - @Override - public Inventory getInventory() { - if (this.inventory == null) { - this.inventory = createInventory(getTitle()); - } + public NamedGui(@NotNull TextHolder title) { + this(title, JavaPlugin.getProvidingPlugin(NamedGui.class)); + } - return inventory; + /** + * Constructs a new gui with a title for the given {@code plugin}. + * + * @param title the title/name of this gui + * @param plugin the owning plugin of this gui + * @see #NamedGui(String) + * @since 0.10.8 + */ + public NamedGui(@NotNull String title, @NotNull Plugin plugin) { + this(StringHolder.of(title), plugin); } - @NotNull - @Override - public Inventory createInventory() { - return createInventory(title); + /** + * Constructs a new gui with a title for the given {@code plugin}. + * + * @param title the title/name of this gui + * @param plugin the owning plugin of this gui + * @see #NamedGui(TextHolder) + * @since 0.10.8 + */ + public NamedGui(@NotNull TextHolder title, @NotNull Plugin plugin) { + super(plugin); + + this.title = title; } /** - * Sets the title for this inventory. This will (unlike most other methods) directly update itself in order - * to ensure all viewers will still be viewing the new inventory as well. + * Sets the title for this inventory. * * @param title the title */ public void setTitle(@NotNull String title) { - //copy the viewers - List viewers = getViewers(); + setTitle(StringHolder.of(title)); + } - this.inventory = createInventory(title); + /** + * Sets the title for this inventory. + * + * @param title the title + * @since 0.10.0 + */ + public void setTitle(@NotNull TextHolder title) { this.title = title; - - updating = true; - - for (HumanEntity viewer : viewers) { - show(viewer); - } - - updating = false; + this.dirty = true; } /** - * Returns the title of this gui + * Returns the title of this gui as a legacy string. * * @return the title * @since 0.8.0 @@ -85,6 +95,39 @@ public void setTitle(@NotNull String title) { @NotNull @Contract(pure = true) public String getTitle() { + return title.asLegacyString(); + } + + /** + * Returns the title of this GUI in a wrapped form. + * + * @return the title + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public TextHolder getTitleHolder() { return title; } + + /** + * Gets whether this title is dirty or not i.e. whether the title has changed. + * + * @return whether the title is dirty + * @since 0.10.0 + */ + @Contract(pure = true) + public boolean isDirty() { + return dirty; + } + + /** + * Marks that the changes present here have been accepted. This sets dirty to false. If dirty was already false, + * this will do nothing. + * + * @since 0.10.0 + */ + public void markChanges() { + this.dirty = false; + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/MasonryPane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/MasonryPane.java index 2b09ca057..d0b4ff0a9 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/MasonryPane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/MasonryPane.java @@ -4,7 +4,10 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -37,10 +40,35 @@ public class MasonryPane extends Pane implements Orientable { @NotNull private Orientation orientation = Orientation.HORIZONTAL; + /** + * Creates a new masonry pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @since 0.10.8 + */ + public MasonryPane(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); + } + public MasonryPane(int x, int y, int length, int height, @NotNull Priority priority) { super(x, y, length, height, priority); } + /** + * Creates a new masonry pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @since 0.10.8 + */ + public MasonryPane(@NotNull Slot slot, int length, int height) { + super(slot, length, height); + } + public MasonryPane(int x, int y, int length, int height) { super(x, y, length, height); } @@ -64,6 +92,10 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs for (int paneIndex = 0; paneIndex < panes.size(); paneIndex++) { Pane pane = panes.get(paneIndex); + if (!pane.isVisible()) { + continue; + } + if (orientation == Orientation.HORIZONTAL) { outerLoop: for (int y = 0; y < height; y++) { @@ -93,8 +125,8 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs pane.display( inventoryComponent, - paneOffsetX + getX(), - paneOffsetY + getY(), + paneOffsetX + getSlot().getX(length), + paneOffsetY + getSlot().getY(length), Math.min(this.length, maxLength), Math.min(this.height, maxHeight) ); @@ -131,8 +163,8 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs pane.display( inventoryComponent, - paneOffsetX + getX(), - paneOffsetY + getY(), + paneOffsetX + getSlot().getX(length), + paneOffsetY + getSlot().getY(length), Math.min(this.length, maxLength), Math.min(this.height, maxHeight) ); @@ -151,10 +183,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; if (x < 0 || x >= length || y < 0 || y >= height) { return false; @@ -164,9 +203,13 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp boolean success = false; - for (Pane pane : panes) { - success = success || pane.click(gui, inventoryComponent, event, slot, paneOffsetX + getX(), - paneOffsetY + getY(), length, height); + for (Pane pane : new ArrayList<>(panes)) { + if (!pane.isVisible()) { + continue; + } + + success = success || pane.click(gui, inventoryComponent, event, slot, paneOffsetX + xPosition, + paneOffsetY + yPosition, length, height); } return success; @@ -176,7 +219,7 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Contract(pure = true) @Override public MasonryPane copy() { - MasonryPane masonryPane = new MasonryPane(x, y, length, height, getPriority()); + MasonryPane masonryPane = new MasonryPane(getSlot(), length, height, getPriority()); for (Pane pane : panes) { masonryPane.addPane(pane.copy()); @@ -241,38 +284,71 @@ public void setOrientation(@NotNull Orientation orientation) { * * @param instance the instance class * @param element the element + * @param plugin the plugin that will be the owner of the created items * @return the masonry pane + * @since 0.10.8 */ @NotNull - public static MasonryPane load(@NotNull Object instance, @NotNull Element element) { + public static MasonryPane load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Masonry pane XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Masonry pane XML tag does not have the mandatory height attribute"); + } + + int length; + int height; + try { - MasonryPane masonryPane = new MasonryPane( - Integer.parseInt(element.getAttribute("length")), - Integer.parseInt(element.getAttribute("height")) - ); + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { + height = Integer.parseInt(element.getAttribute("height")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Height attribute is not an integer", exception); + } - Pane.load(masonryPane, instance, element); - Orientable.load(masonryPane, element); + MasonryPane masonryPane = new MasonryPane(length, height); - if (element.hasAttribute("populate")) { - return masonryPane; - } + Pane.load(masonryPane, instance, element); + Orientable.load(masonryPane, element); - NodeList childNodes = element.getChildNodes(); + if (element.hasAttribute("populate")) { + return masonryPane; + } - for (int j = 0; j < childNodes.getLength(); j++) { - Node pane = childNodes.item(j); + NodeList childNodes = element.getChildNodes(); - if (pane.getNodeType() != Node.ELEMENT_NODE) { - continue; - } + for (int j = 0; j < childNodes.getLength(); j++) { + Node pane = childNodes.item(j); - masonryPane.addPane(Gui.loadPane(instance, pane)); + if (pane.getNodeType() != Node.ELEMENT_NODE) { + continue; } - return masonryPane; - } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + masonryPane.addPane(Gui.loadPane(instance, pane, plugin)); } + + return masonryPane; + } + + /** + * Loads a masonry pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the masonry pane + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static MasonryPane load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(MasonryPane.class)); } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Orientable.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Orientable.java index bc5ccc4ef..d2a64576e 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Orientable.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Orientable.java @@ -1,5 +1,6 @@ package com.github.stefvanschie.inventoryframework.pane; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -41,8 +42,12 @@ public interface Orientable { */ static void load(@NotNull Orientable orientable, @NotNull Element element) { if (element.hasAttribute("orientation")) { - orientable.setOrientation(Orientation.valueOf(element.getAttribute("orientation") - .toUpperCase(Locale.getDefault()))); + try { + orientable.setOrientation(Orientation.valueOf(element.getAttribute("orientation") + .toUpperCase(Locale.getDefault()))); + } catch (IllegalArgumentException exception) { + throw new XMLLoadException("Orientation does not have a proper value", exception); + } } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/OutlinePane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/OutlinePane.java index fdda3e95d..7573c8785 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/OutlinePane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/OutlinePane.java @@ -5,10 +5,13 @@ import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.pane.util.Mask; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import com.github.stefvanschie.inventoryframework.util.GeometryUtil; import org.bukkit.Material; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -54,14 +57,29 @@ public class OutlinePane extends Pane implements Flippable, Orientable, Rotatabl */ private boolean flipHorizontally, flipVertically; + /** + * The alignment of this pane + */ + @NotNull + private Alignment alignment = Alignment.BEGIN; + /** * The mask for this pane */ @NotNull private Mask mask; - public OutlinePane(int x, int y, int length, int height, @NotNull Priority priority) { - super(x, y, length, height, priority); + /** + * Creates a new outline pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @since 0.10.8 + */ + public OutlinePane(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); this.items = new ArrayList<>(length * height); this.orientation = Orientation.HORIZONTAL; @@ -78,6 +96,23 @@ public OutlinePane(int x, int y, int length, int height, @NotNull Priority prior this.mask = new Mask(mask); } + public OutlinePane(int x, int y, int length, int height, @NotNull Priority priority) { + this(Slot.fromXY(x, y), length, height, priority); + } + + + /** + * Creates a new outline pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @since 0.10.8 + */ + public OutlinePane(@NotNull Slot slot, int length, int height) { + this(slot, length, height, Priority.NORMAL); + } + public OutlinePane(int x, int y, int length, int height) { this(x, y, length, height, Priority.NORMAL); } @@ -92,98 +127,123 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int x = 0, y = 0; + int itemIndex = 0; + int gapCount = 0; - if (orientation == Orientation.HORIZONTAL) { - outerloop: - for (int rowIndex = 0; rowIndex < inventoryComponent.getHeight(); rowIndex++) { - boolean[] row = mask.getRow(rowIndex); + int size; - for (int columnIndex = 0; columnIndex < inventoryComponent.getLength(); columnIndex++) { - if (!row[columnIndex]) { - continue; - } + if (getOrientation() == Orientation.HORIZONTAL) { + size = height; + } else if (getOrientation() == Orientation.VERTICAL) { + size = length; + } else { + throw new IllegalStateException("Unknown orientation '" + getOrientation() + "'"); + } - x = columnIndex; - y = rowIndex; - break outerloop; - } + for (int vectorIndex = 0; vectorIndex < size && getItems().size() > itemIndex; vectorIndex++) { + boolean[] maskLine; + + if (getOrientation() == Orientation.HORIZONTAL) { + maskLine = mask.getRow(vectorIndex); + } else if (getOrientation() == Orientation.VERTICAL) { + maskLine = mask.getColumn(vectorIndex); + } else { + throw new IllegalStateException("Unknown orientation '" + getOrientation() + "'"); } - } else if (orientation == Orientation.VERTICAL) { - outerloop: - for (int columnIndex = 0; columnIndex < inventoryComponent.getLength(); columnIndex++) { - boolean[] column = mask.getColumn(columnIndex); - - for (int rowIndex = 0; rowIndex < inventoryComponent.getHeight(); rowIndex++) { - if (!column[rowIndex]) { - continue; - } - x = columnIndex; - y = rowIndex; - break outerloop; + int enabled = 0; + + for (boolean bool : maskLine) { + if (bool) { + enabled++; } } - } - int itemAmount = items.size(); + GuiItem[] items; - outerloop: - for (int i = 0; i < (doesRepeat() ? Math.max(mask.amountOfEnabledSlots(), inventoryComponent.getSize()) : itemAmount); i++) { - GuiItem item = items.get(i % itemAmount); + if (doesRepeat()) { + items = new GuiItem[enabled]; + } else { + int remainingPositions = gapCount + (getItems().size() - itemIndex - 1) * (getGap() + 1) + 1; - if (!item.isVisible()) - continue; + items = new GuiItem[Math.min(enabled, remainingPositions)]; + } - int newX = x, newY = y; + for (int index = 0; index < items.length; index++) { + if (gapCount == 0) { + items[index] = getItems().get(itemIndex); - if (flipHorizontally) - newX = length - x - 1; + itemIndex++; - if (flipVertically) - newY = height - y - 1; + if (doesRepeat() && itemIndex >= getItems().size()) { + itemIndex = 0; + } - Map.Entry coordinates = GeometryUtil.processClockwiseRotation(newX, newY, length, height, - rotation); + gapCount = getGap(); + } else { + items[index] = null; - newX = coordinates.getKey(); - newY = coordinates.getValue(); + gapCount--; + } + } - if (newX >= 0 && newX < length && newY >= 0 && newY < height) { - int finalRow = getY() + newY + paneOffsetY; - int finalColumn = getX() + newX + paneOffsetX; + int index; - inventoryComponent.setItem(item, finalColumn, finalRow); + if (getAlignment() == Alignment.BEGIN) { + index = 0; + } else if (getAlignment() == Alignment.CENTER) { + index = -((enabled - items.length) / 2); + } else { + throw new IllegalStateException("Unknown alignment '" + getAlignment() + "'"); } - int gapCount = gap; + for (int opposingVectorIndex = 0; opposingVectorIndex < maskLine.length; opposingVectorIndex++) { + if (!maskLine[opposingVectorIndex]) { + continue; + } - do { - if (orientation == Orientation.HORIZONTAL) { - x++; + if (index >= 0 && index < items.length && items[index] != null) { + int x, y; + + if (getOrientation() == Orientation.HORIZONTAL) { + x = opposingVectorIndex; + y = vectorIndex; + } else if (getOrientation() == Orientation.VERTICAL) { + x = vectorIndex; + y = opposingVectorIndex; + } else { + throw new IllegalStateException("Unknown orientation '" + getOrientation() + "'"); + } - if (x >= length) { - y++; - x = 0; + if (flipHorizontally) { + x = length - x - 1; } - } else if (orientation == Orientation.VERTICAL) { - y++; - if (y >= height) { - x++; - y = 0; + if (flipVertically) { + y = height - y - 1; } - } - //stop the loop when there is no more space in the pane - if (x >= length || y >= height) { - break outerloop; - } + Map.Entry coordinates = GeometryUtil.processClockwiseRotation(x, y, + length, height, rotation); - if (mask.isEnabled(x, y)) { - gapCount--; + x = coordinates.getKey(); + y = coordinates.getValue(); + + if (x >= 0 && x < length && y >= 0 && y < height) { + Slot slot = getSlot(); + + int finalRow = slot.getY(maxLength) + y + paneOffsetY; + int finalColumn = slot.getX(maxLength) + x + paneOffsetX; + + GuiItem item = items[index]; + if (item.isVisible()) { + inventoryComponent.setItem(item, finalColumn, finalRow); + } + } } - } while (gapCount >= 0); + + index++; + } } } @@ -194,10 +254,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { @@ -227,7 +294,7 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Contract(pure = true) @Override public OutlinePane copy() { - OutlinePane outlinePane = new OutlinePane(x, y, length, height, getPriority()); + OutlinePane outlinePane = new OutlinePane(getSlot(), length, height, getPriority()); for (GuiItem item : items) { outlinePane.addItem(item.copy()); @@ -245,6 +312,7 @@ public OutlinePane copy() { outlinePane.flipHorizontally = flipHorizontally; outlinePane.flipVertically = flipVertically; outlinePane.mask = mask; + outlinePane.alignment = alignment; return outlinePane; } @@ -326,6 +394,16 @@ public void setHeight(int height) { applyMask(getMask().setHeight(height)); } + /** + * Aligns the pane in the way specified by the provided alignment. + * + * @param alignment the new alignment + * @since 0.10.1 + */ + public void align(@NotNull Alignment alignment) { + this.alignment = alignment; + } + @Override public void flipHorizontally(boolean flipHorizontally) { this.flipHorizontally = flipHorizontally; @@ -366,6 +444,18 @@ public Collection getPanes() { return new HashSet<>(); } + /** + * Gets the alignment set on this pane. + * + * @return the alignment + * @since 0.10.1 + */ + @NotNull + @Contract(pure = true) + public Alignment getAlignment() { + return this.alignment; + } + /** * Gets whether this outline pane repeats itself * @@ -439,47 +529,116 @@ public boolean isFlippedVertically() { * * @param instance the instance class * @param element the element + * @param plugin the plugin that will be the owner of the created items * @return the outline pane + * @since 0.10.8 */ @NotNull - public static OutlinePane load(@NotNull Object instance, @NotNull Element element) { - try { - OutlinePane outlinePane = new OutlinePane( - Integer.parseInt(element.getAttribute("length")), - Integer.parseInt(element.getAttribute("height")) - ); + public static OutlinePane load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Outline pane XML tag does not have the mandatory length attribute"); + } - if (element.hasAttribute("gap")) - outlinePane.setGap(Integer.parseInt(element.getAttribute("gap"))); + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Outline pane XML tag does not have the mandatory height attribute"); + } - if (element.hasAttribute("repeat")) - outlinePane.setRepeat(Boolean.parseBoolean(element.getAttribute("repeat"))); + int length; + int height; - Pane.load(outlinePane, instance, element); - Flippable.load(outlinePane, element); - Orientable.load(outlinePane, element); - Rotatable.load(outlinePane, element); + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } - if (element.hasAttribute("populate")) - return outlinePane; + try { + height = Integer.parseInt(element.getAttribute("height")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Height attribute is not an integer", exception); + } - NodeList childNodes = element.getChildNodes(); + OutlinePane outlinePane = new OutlinePane(length, height); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.item(i); + if (element.hasAttribute("gap")) { + try { + outlinePane.setGap(Integer.parseInt(element.getAttribute("gap"))); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Gap attribute is not an integer", exception); + } + } - if (item.getNodeType() != Node.ELEMENT_NODE) - continue; + if (element.hasAttribute("repeat")) + outlinePane.setRepeat(Boolean.parseBoolean(element.getAttribute("repeat"))); - if (item.getNodeName().equals("empty")) - outlinePane.addItem(new GuiItem(new ItemStack(Material.AIR))); - else - outlinePane.addItem(Pane.loadItem(instance, (Element) item)); + if (element.hasAttribute("alignment")) { + try { + outlinePane.align(Alignment.valueOf(element.getAttribute("alignment").toUpperCase())); + } catch (IllegalArgumentException exception) { + throw new XMLLoadException("Alignment attribute is not a proper value", exception); } + } + + Pane.load(outlinePane, instance, element); + Flippable.load(outlinePane, element); + Orientable.load(outlinePane, element); + Rotatable.load(outlinePane, element); + if (element.hasAttribute("populate")) return outlinePane; - } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + + NodeList childNodes = element.getChildNodes(); + + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + + if (item.getNodeType() != Node.ELEMENT_NODE) + continue; + + if (item.getNodeName().equals("empty")) + outlinePane.addItem(new GuiItem(new ItemStack(Material.AIR), plugin)); + else + outlinePane.addItem(Pane.loadItem(instance, (Element) item, plugin)); } + + return outlinePane; + } + + /** + * Loads an outline pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the outline pane + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static OutlinePane load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(OutlinePane.class)); + } + + /** + * An enum containing different alignments that can be used on the outline pane. + * + * @since 0.10.1 + */ + public enum Alignment { + + /** + * Aligns the items at the start of the pane. + * + * @since 0.10.1 + */ + BEGIN, + + /** + * Aligns the items in the center of the pane. If there is no exact center, this will preference the left (for a + * horizontal orientation) or the top (for a vertical orientation). + * + * @since 0.10.1 + */ + CENTER } -} \ No newline at end of file +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPane.java index 2237e0a61..fc9eb57f5 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPane.java @@ -4,11 +4,14 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,15 +31,40 @@ public class PaginatedPane extends Pane { * A set of panes for the different pages */ @NotNull - private final Map> panes = new HashMap<>(); + private final List> panes = new ArrayList<>(); /** * The current page */ private int page; + /** + * Creates a new paginated pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @since 0.10.8 + */ + public PaginatedPane(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); + } + public PaginatedPane(int x, int y, int length, int height, @NotNull Priority priority) { - super(x, y, length, height, priority); + this(Slot.fromXY(x, y), length, height, priority); + } + + /** + * Creates a new paginated pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @since 0.10.8 + */ + public PaginatedPane(@NotNull Slot slot, int length, int height) { + this(slot, length, height, Priority.NORMAL); } public PaginatedPane(int x, int y, int length, int height) { @@ -64,29 +92,73 @@ public int getPage() { public int getPages() { return panes.size(); } + + /** + * Adds the specified pane to a new page. The new page will be at the index one after the highest indexed page + * currently in this paginated pane. If this paginated pane has no pages, the index of the newly created page will + * be zero. + * + * @param pane the pane to add to a new page + * @since 0.10.8 + */ + public void addPage(@NotNull Pane pane) { + List list = new ArrayList<>(1); + + list.add(pane); + + this.panes.add(list); + } + /** - * Assigns a pane to a selected page + * Adds a pane to a selected page. If the page does not exist and is exactly one larger than the current highest + * page index, this method will create a new page with the specified pane. If the page does not exist and is more + * than one larger than the current highest page index, this method will throw an {@link IllegalArgumentException}. + * If the page is negative, an {@link IllegalArgumentException} will also be thrown. If there are currently no + * pages, only index 0 is valid and will create a new page with the specified pane added to it. + *

+ * For example, if the pages 0, 1, ..., n currently exist, then: + *

    + *
  • Indexes < 0 will throw an exception
  • + *
  • Indexes 0, 1, ..., n will add the pane to the respective page
  • + *
  • Index n + 1 will create a new page with the specified pane
  • + *
  • Indexes > n + 1 will throw an exception
  • + *
* * @param page the page to assign the pane to * @param pane the new pane + * @throws IllegalArgumentException if the page is less than 0 or more than one larger than the current highest page + * index */ public void addPane(int page, @NotNull Pane pane) { - if (!this.panes.containsKey(page)) - this.panes.put(page, new ArrayList<>()); + if (page < 0) { + throw new IllegalArgumentException("Non-positive page indexes are not allowed"); + } + + if (page > this.panes.size()) { + throw new IllegalArgumentException("Page index outside range of existing pages"); + } - this.panes.get(page).add(pane); + if (page == this.panes.size()) { + addPage(pane); + } else { + this.panes.get(page).add(pane); - this.panes.get(page).sort(Comparator.comparing(Pane::getPriority)); + this.panes.get(page).sort(Comparator.comparing(Pane::getPriority)); + } } /** - * Sets the current displayed page + * Sets the current displayed page. If the specified page does not exist an {@link ArrayIndexOutOfBoundsException} + * is thrown. * * @param page the page + * @throws ArrayIndexOutOfBoundsException if the page does not exist */ public void setPage(int page) { - if (!panes.containsKey(page)) - throw new ArrayIndexOutOfBoundsException("page outside range"); + if (page < 0 || page >= this.panes.size()) { + throw new ArrayIndexOutOfBoundsException("Page outside of range"); + } + this.page = page; } @@ -95,9 +167,11 @@ public void setPage(int page) { * This can be helpful when dealing with lists of unknown size. * * @param items The list to populate the pane with + * @param plugin the plugin that will be the owner of the items created + * @see #populateWithItemStacks(List) + * @since 0.10.8 */ - @Contract("null -> fail") - public void populateWithItemStacks(@NotNull List items) { + public void populateWithItemStacks(@NotNull List<@NotNull ItemStack> items, @NotNull Plugin plugin) { //Don't do anything if the list is empty if (items.isEmpty()) { return; @@ -117,13 +191,22 @@ public void populateWithItemStacks(@NotNull List items) { break; } - page.addItem(new GuiItem(items.get(index))); + page.addItem(new GuiItem(items.get(index), plugin)); } this.addPane(i, page); } } + /** + * Populates the PaginatedPane based on the provided list by adding new pages until all items can fit. + * This can be helpful when dealing with lists of unknown size. + * + * @param items The list to populate the pane with + */ + public void populateWithItemStacks(@NotNull List items) { + populateWithItemStacks(items, JavaPlugin.getProvidingPlugin(PaginatedPane.class)); + } /** * Populates the PaginatedPane based on the provided list by adding new pages until all items can fit. @@ -166,9 +249,12 @@ public void populateWithGuiItems(@NotNull List items) { * * @param displayNames The display names for all the items * @param material The material to use for the {@link org.bukkit.inventory.ItemStack}s + * @param plugin the plugin that will be the owner of the created items + * @see #populateWithNames(List, Material) + * @since 0.10.8 */ - @Contract("null, _ -> fail") - public void populateWithNames(@NotNull List displayNames, @Nullable Material material) { + public void populateWithNames(@NotNull List displayNames, @Nullable Material material, + @NotNull Plugin plugin) { if(material == null || material == Material.AIR) return; populateWithItemStacks(displayNames.stream().map(name -> { @@ -177,12 +263,28 @@ public void populateWithNames(@NotNull List displayNames, @Nullable Mate itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name)); itemStack.setItemMeta(itemMeta); return itemStack; - }).collect(Collectors.toList())); + }).collect(Collectors.toList()), plugin); } + /** + * This method creates a list of ItemStacks all with the given {@code material} and the display names. + * After that it calls {@link #populateWithItemStacks(List)} + * This method also translates the color char {@code &} for all names. + * + * @param displayNames The display names for all the items + * @param material The material to use for the {@link org.bukkit.inventory.ItemStack}s + */ + public void populateWithNames(@NotNull List displayNames, @Nullable Material material) { + populateWithNames(displayNames, material, JavaPlugin.getProvidingPlugin(PaginatedPane.class)); + } + @Override public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { + if (this.page < 0 || this.page >= this.panes.size()) { + return; + } + List panes = this.panes.get(page); if (panes == null) { @@ -190,8 +292,14 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs } for (Pane pane : panes) { - int newPaneOffsetX = paneOffsetX + getX(); - int newPaneOffsetY = paneOffsetY + getY(); + if (!pane.isVisible()) { + continue; + } + + Slot slot = getSlot(); + + int newPaneOffsetX = paneOffsetX + slot.getX(maxLength); + int newPaneOffsetY = paneOffsetY + slot.getY(maxLength); int newMaxLength = Math.min(length, maxLength); int newMaxHeight = Math.min(height, maxHeight); @@ -206,10 +314,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { @@ -220,9 +335,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp boolean success = false; - for (Pane pane : this.panes.getOrDefault(page, Collections.emptyList())) { - success = success || pane.click(gui, inventoryComponent, event, slot,paneOffsetX + getX(), - paneOffsetY + getY(), length, height); + if (this.page <= 0 || this.page >= this.panes.size()) { + return false; + } + + for (Pane pane : this.panes.get(this.page)) { + if (!pane.isVisible()) { + continue; + } + + success = success || pane.click(gui, inventoryComponent, event, slot,paneOffsetX + xPosition, + paneOffsetY + yPosition, length, height); } return success; @@ -232,34 +355,52 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Contract(pure = true) @Override public PaginatedPane copy() { - PaginatedPane paginatedPane = new PaginatedPane(x, y, length, height, getPriority()); + PaginatedPane paginatedPane = new PaginatedPane(getSlot(), this.length, this.height, getPriority()); + + for (int page = 0; page < this.panes.size(); page++) { + List panes = this.panes.get(page); - for (Map.Entry> entry : panes.entrySet()) { - for (Pane pane : entry.getValue()) { - paginatedPane.addPane(entry.getKey(), pane.copy()); + for (Pane pane : panes) { + paginatedPane.addPane(page, pane.copy()); } } paginatedPane.setVisible(isVisible()); - paginatedPane.onClick = onClick; + paginatedPane.onClick = this.onClick; - paginatedPane.uuid = uuid; + paginatedPane.uuid = this.uuid; - paginatedPane.page = page; + paginatedPane.page = this.page; return paginatedPane; } + /** + * Deletes a page and all its associated panes from this paginated pane. It also decrements the indexes of all pages + * beyond the specified page by one. For example, given a sequence of pages 0, 1, 2, 3, 4, upon removing page 2, the + * new sequence of pages will be 0, 1, 2, 3. If the specified page does not exist, then this method will silently do + * nothing. + * + * @param page the page to delete + * @since 0.10.5 + */ + public void deletePage(int page) { + if (page < 0 || page >= this.panes.size()) { + return; + } + + this.panes.remove(page); + } + @NotNull @Contract(pure = true) @Override public Collection getPanes() { Collection panes = new HashSet<>(); - this.panes.forEach((integer, p) -> { - p.forEach(pane -> panes.addAll(pane.getPanes())); + for (List p : this.panes) { panes.addAll(p); - }); + } return panes; } @@ -280,6 +421,10 @@ public Collection getPanes() { @NotNull @Contract(pure = true) public Collection getPanes(int page) { + if (page < 0 || this.page >= this.panes.size()) { + throw new IllegalArgumentException("Invalid page"); + } + Collection panes = this.panes.get(page); if (panes == null) { @@ -306,48 +451,89 @@ public void clear() { * * @param instance the instance class * @param element the element + * @param plugin the plugin that will be used to create the items * @return the paginated pane + * @since 0.10.8 */ @NotNull - public static PaginatedPane load(@NotNull Object instance, @NotNull Element element) { + public static PaginatedPane load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Paginated pane XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Paginated pane XML tag does not have the mandatory height attribute"); + } + + int length; + int height; + + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + try { - PaginatedPane paginatedPane = new PaginatedPane( - Integer.parseInt(element.getAttribute("length")), - Integer.parseInt(element.getAttribute("height")) - ); + height = Integer.parseInt(element.getAttribute("height")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Height attribute is not an integer", exception); + } - Pane.load(paginatedPane, instance, element); + PaginatedPane paginatedPane = new PaginatedPane(length, height); - if (element.hasAttribute("populate")) - return paginatedPane; + Pane.load(paginatedPane, instance, element); - int pageCount = 0; + if (element.hasAttribute("id")) { + element.setIdAttribute("id", true); + element.setUserData("pane", paginatedPane, null); + } + + if (element.hasAttribute("populate")) { + return paginatedPane; + } - NodeList childNodes = element.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.item(i); + int pageCount = 0; - if (item.getNodeType() != Node.ELEMENT_NODE) - continue; + NodeList childNodes = element.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (item.getNodeType() != Node.ELEMENT_NODE) + continue; - NodeList innerNodes = item.getChildNodes(); + if(!item.getNodeName().equals("page")) + throw new XMLLoadException("Panes have to be inside page tag"); - for (int j = 0; j < innerNodes.getLength(); j++) { - Node pane = innerNodes.item(j); + NodeList innerNodes = item.getChildNodes(); - if (pane.getNodeType() != Node.ELEMENT_NODE) { - continue; - } + for (int j = 0; j < innerNodes.getLength(); j++) { + Node pane = innerNodes.item(j); - paginatedPane.addPane(pageCount, Gui.loadPane(instance, pane)); + if (pane.getNodeType() != Node.ELEMENT_NODE) { + continue; } - pageCount++; + paginatedPane.addPane(pageCount, Gui.loadPane(instance, pane, plugin)); } - return paginatedPane; - } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + pageCount++; } + + return paginatedPane; + } + + /** + * Loads a paginated pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the paginated pane + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static PaginatedPane load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(PaginatedPane.class)); } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Pane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Pane.java index 79a7afed8..cf654807a 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Pane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Pane.java @@ -1,16 +1,18 @@ package com.github.stefvanschie.inventoryframework.pane; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.exception.XMLReflectionException; +import com.github.stefvanschie.inventoryframework.pane.util.Mask; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; +import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil; import com.github.stefvanschie.inventoryframework.util.SkullUtil; import com.github.stefvanschie.inventoryframework.util.UUIDTagType; import com.github.stefvanschie.inventoryframework.util.XMLUtil; import com.google.common.primitives.Primitives; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; @@ -18,6 +20,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -32,8 +35,6 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Function; -import java.util.logging.Level; -import java.util.logging.Logger; /** * The base class for all panes. @@ -43,8 +44,15 @@ public abstract class Pane { /** * The starting position of this pane, which is 0 by default */ + @Deprecated protected int x = 0, y = 0; + /** + * The position of this pane, which is (0,0) by default + */ + @NotNull + protected Slot slot = Slot.fromXY(0, 0); + /** * Length is horizontal, height is vertical */ @@ -81,19 +89,18 @@ public abstract class Pane { /** * Constructs a new default pane * - * @param x the upper left x coordinate of the pane - * @param y the upper left y coordinate of the pane + * @param slot the slot of the pane * @param length the length of the pane * @param height the height of the pane * @param priority the priority of the pane + * @since 0.10.8 */ - protected Pane(int x, int y, int length, int height, @NotNull Priority priority) { + protected Pane(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { if (length == 0 || height == 0) { throw new IllegalArgumentException("Length and height of pane must be greater than zero"); } - this.x = x; - this.y = y; + setSlot(slot); this.length = length; this.height = height; @@ -104,6 +111,19 @@ protected Pane(int x, int y, int length, int height, @NotNull Priority priority) this.uuid = UUID.randomUUID(); } + /** + * Constructs a new default pane + * + * @param x the upper left x coordinate of the pane + * @param y the upper left y coordinate of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + */ + protected Pane(int x, int y, int length, int height, @NotNull Priority priority) { + this(Slot.fromXY(x, y), length, height, priority); + } + /** * Constructs a new default pane, with no position * @@ -124,6 +144,17 @@ protected Pane(int length, int height) { this.uuid = UUID.randomUUID(); } + /** + * Constructs a new default pane + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + */ + protected Pane(Slot slot, int length, int height) { + this(slot, length, height, Priority.NORMAL); + } + /** * Constructs a new default pane * @@ -167,6 +198,20 @@ public void setHeight(int height) { this.height = height; } + /** + * Sets the slot of this pane. + * + * @param slot the slot + * @since 0.10.8 + */ + public void setSlot(@NotNull Slot slot) { + this.slot = slot; + + //the length should be the length of the parent container, but we don't have that, so just use one + this.x = slot.getX(1); + this.y = slot.getY(1); + } + /** * Set the x coordinate of this pane * @@ -174,6 +219,8 @@ public void setHeight(int height) { */ public void setX(int x) { this.x = x; + + this.slot = Slot.fromXY(x, getY()); } /** @@ -183,6 +230,8 @@ public void setX(int x) { */ public void setY(int y) { this.y = y; + + this.slot = Slot.fromXY(getX(), y); } /** @@ -217,12 +266,27 @@ public UUID getUUID() { return uuid; } + /** + * Gets the slot of the position of this pane + * + * @return the slot + * @since 0.10.8 + */ + @NotNull + @Contract(pure = true) + public Slot getSlot() { + return this.slot; + } + /** * Gets the x coordinate of this pane * * @return the x coordinate + * @deprecated when the slot was specified as an indexed position, this may return the wrong value; + * {@link #getSlot()} should be used instead */ @Contract(pure = true) + @Deprecated public int getX() { return x; } @@ -231,8 +295,11 @@ public int getX() { * Gets the y coordinate of this pane * * @return the y coordinate + * @deprecated when the slot was specified as an indexed position, this may return the wrong value; + * {@link #getSlot()} should be used instead */ @Contract(pure = true) + @Deprecated public int getY() { return y; } @@ -299,17 +366,41 @@ public void setPriority(@NotNull Priority priority) { * * @param instance the instance * @param element the element + * @param plugin the plugin that will be the owner of the created item * @return the gui item + * @see #loadItem(Object, Element) + * @since 0.10.8 */ @NotNull @Contract(pure = true) - public static GuiItem loadItem(@NotNull Object instance, @NotNull Element element) { + public static GuiItem loadItem(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { String id = element.getAttribute("id"); - Material material = Objects.requireNonNull(Material.matchMaterial(id.toUpperCase(Locale.getDefault()))); - boolean hasAmount = element.hasAttribute("amount"); + Material material = Material.matchMaterial(id.toUpperCase(Locale.getDefault())); + + if (material == null) { + throw new XMLLoadException("Can't find material for '" + id + "'"); + } + boolean hasDamage = element.hasAttribute("damage"); - int amount = hasAmount ? Integer.parseInt(element.getAttribute("amount")) : 1; - short damage = hasDamage ? Short.parseShort(element.getAttribute("damage")) : 0; + int amount = 1; + + if (element.hasAttribute("amount")) { + try { + amount = Integer.parseInt(element.getAttribute("amount")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Amount attribute is not an integer", exception); + } + } + + short damage = 0; + + if (element.hasAttribute("damage")) { + try { + amount = Short.parseShort(element.getAttribute("damage")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Damage attribute is not a short", exception); + } + } //noinspection deprecation ItemStack itemStack = new ItemStack(material, amount, damage); @@ -351,25 +442,30 @@ public static GuiItem loadItem(@NotNull Object instance, @NotNull Element elemen ? innerElementChild.getAttribute("type") : "string"; - properties.add(PROPERTY_MAPPINGS.get(propertyType).apply(innerElementChild - .getTextContent())); + Function mapping = PROPERTY_MAPPINGS.get(propertyType); + + if (mapping == null) { + throw new XMLLoadException("Specified property type is not registered"); + } + + properties.add(mapping.apply(innerElementChild.getTextContent())); break; case "lore": if (!innerNode.getNodeName().equals("line")) continue; - boolean hasLore = itemMeta.hasLore(); - List lore = hasLore ? Objects.requireNonNull(itemMeta.getLore()) : new ArrayList<>(); - - lore.add(ChatColor.translateAlternateColorCodes('&', innerNode - .getTextContent())); - itemMeta.setLore(lore); + TextHolder.deserialize(innerNode.getTextContent()) + .asItemLoreAtEnd(itemMeta); itemStack.setItemMeta(itemMeta); break; case "enchantments": if (!innerNode.getNodeName().equals("enchantment")) continue; + if (!innerElementChild.hasAttribute("id")) { + throw new XMLLoadException("Enchantment tag does not have mandatory id attribute"); + } + Enchantment enchantment = Enchantment.getByKey(NamespacedKey.minecraft( innerElementChild.getAttribute("id").toUpperCase(Locale.getDefault()) )); @@ -378,7 +474,19 @@ public static GuiItem loadItem(@NotNull Object instance, @NotNull Element elemen throw new XMLLoadException("Enchantment cannot be found"); } - int level = Integer.parseInt(innerElementChild.getAttribute("level")); + if (!element.hasAttribute("level")) { + throw new XMLLoadException( + "Enchantment tag does not have mandatory level attribute" + ); + } + + int level; + + try { + level = Integer.parseInt(innerElementChild.getAttribute("level")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Level attribute is not an integer", exception); + } itemMeta.addEnchant(enchantment, level, true); itemStack.setItemMeta(itemMeta); @@ -388,8 +496,18 @@ public static GuiItem loadItem(@NotNull Object instance, @NotNull Element elemen } else if (nodeName.equals("displayname")) { ItemMeta itemMeta = Objects.requireNonNull(itemStack.getItemMeta()); - itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', item - .getTextContent())); + TextHolder.deserialize(item.getTextContent()) + .asItemDisplayName(itemMeta); + + itemStack.setItemMeta(itemMeta); + } else if (nodeName.equals("modeldata")) { + ItemMeta itemMeta = Objects.requireNonNull(itemStack.getItemMeta()); + + try { + itemMeta.setCustomModelData(Integer.parseInt(item.getTextContent())); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Modeldata tag does not contain an integer", exception); + } itemStack.setItemMeta(itemMeta); } else if (nodeName.equals("skull") && itemStack.getItemMeta() instanceof SkullMeta) { @@ -400,9 +518,13 @@ public static GuiItem loadItem(@NotNull Object instance, @NotNull Element elemen skullMeta.setOwner(elementItem.getAttribute("owner")); else if (elementItem.hasAttribute("id")) { SkullUtil.setSkull(skullMeta, elementItem.getAttribute("id")); + } else { + throw new XMLLoadException("Skull tag has neither an owner, nor an id attribute"); } itemStack.setItemMeta(skullMeta); + } else { + throw new XMLLoadException("Unknown node " + nodeName); } } } @@ -411,6 +533,9 @@ else if (elementItem.hasAttribute("id")) { if (element.hasAttribute("onClick")) { String methodName = element.getAttribute("onClick"); + + boolean found = false; + for (Method method : instance.getClass().getMethods()) { if (!method.getName().equals(methodName)) continue; @@ -418,7 +543,7 @@ else if (elementItem.hasAttribute("id")) { int parameterCount = method.getParameterCount(); Class[] parameterTypes = method.getParameterTypes(); - if (parameterCount == 0) + if (parameterCount == 0) { action = event -> { try { //because reflection with lambdas is stupid @@ -428,8 +553,9 @@ else if (elementItem.hasAttribute("id")) { throw new XMLReflectionException(exception); } }; - else if (parameterTypes[0].isAssignableFrom(InventoryClickEvent.class)) { - if (parameterCount == 1) + found = true; + } else if (parameterTypes[0].isAssignableFrom(InventoryClickEvent.class)) { + if (parameterCount == 1) { action = event -> { try { //because reflection with lambdas is stupid @@ -439,15 +565,16 @@ else if (parameterTypes[0].isAssignableFrom(InventoryClickEvent.class)) { throw new XMLReflectionException(exception); } }; - else if (parameterCount == properties.size() + 1) { + found = true; + } else if (parameterCount == properties.size() + 1) { boolean correct = true; for (int i = 0; i < properties.size(); i++) { Object attribute = properties.get(i); if (!(parameterTypes[1 + i].isPrimitive() && - Primitives.unwrap(attribute.getClass()).isAssignableFrom(parameterTypes[1 + i])) && - !attribute.getClass().isAssignableFrom(parameterTypes[1 + i])) + parameterTypes[1 + i].isAssignableFrom(Primitives.unwrap(attribute.getClass()))) && + !parameterTypes[1 + i].isAssignableFrom(attribute.getClass())) correct = false; } @@ -467,25 +594,26 @@ else if (parameterCount == properties.size() + 1) { throw new XMLReflectionException(exception); } }; + found = true; } } } break; } + + if (!found) { + throw new XMLLoadException("Specified method could not be found"); + } } - GuiItem item = new GuiItem(itemStack, action); + GuiItem item = new GuiItem(itemStack, action, plugin); if (element.hasAttribute("field")) XMLUtil.loadFieldAttribute(instance, element, item); if (element.hasAttribute("populate")) { - try { - MethodUtils.invokeExactMethod(instance, "populate", item, GuiItem.class); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { - throw new XMLLoadException(exception); - } + XMLUtil.invokeMethod(instance, element.getAttribute("populate"), item, GuiItem.class); } item.setProperties(properties); @@ -493,18 +621,30 @@ else if (parameterCount == properties.size() + 1) { return item; } + /** + * Loads an item from an instance and an element + * + * @param instance the instance + * @param element the element + * @return the gui item + */ + @NotNull + @Contract(pure = true) + public static GuiItem loadItem(@NotNull Object instance, @NotNull Element element) { + return loadItem(instance, element, JavaPlugin.getProvidingPlugin(Pane.class)); + } + public static void load(@NotNull Pane pane, @NotNull Object instance, @NotNull Element element) { - if (element.hasAttribute("x")) { - pane.setX(Integer.parseInt(element.getAttribute("x"))); - } + pane.setSlot(Slot.deserialize(element)); - if (element.hasAttribute("y")) { - pane.setY(Integer.parseInt(element.getAttribute("y"))); + if (element.hasAttribute("priority")) { + try { + pane.setPriority(Priority.valueOf(element.getAttribute("priority").toUpperCase())); + } catch (IllegalArgumentException exception) { + throw new XMLLoadException("Priority attribute is not a proper value", exception); + } } - if (element.hasAttribute("priority")) - pane.setPriority(Priority.valueOf(element.getAttribute("priority"))); - if (element.hasAttribute("visible")) pane.setVisible(Boolean.parseBoolean(element.getAttribute("visible"))); @@ -516,6 +656,8 @@ public static void load(@NotNull Pane pane, @NotNull Object instance, @NotNull E if (element.hasAttribute("populate")) { String attribute = element.getAttribute("populate"); + boolean found = false; + for (Method method: instance.getClass().getMethods()) { if (!method.getName().equals(attribute)) continue; @@ -526,10 +668,38 @@ public static void load(@NotNull Pane pane, @NotNull Object instance, @NotNull E } catch (IllegalAccessException | InvocationTargetException exception) { throw new XMLLoadException(exception); } + + found = true; + } + + if (!found) { + throw new XMLLoadException("Specified method could not be found"); } } } + /** + * Checks whether a {@link GuiItem} is the same item as the given {@link ItemStack}. The item will be compared using + * internal data. When the item does not have this data, this method will return false. If the item does have such + * data, but its value does not match, false is also returned. This method will not mutate any of the provided + * arguments. + * + * @param guiItem the gui item to check + * @param item the item which the gui item should be checked against + * @return true if the {@link GuiItem} matches the {@link ItemStack}, false otherwise + * @since 0.10.14 + */ + @Contract(pure = true) + protected static boolean matchesItem(@NotNull GuiItem guiItem, @NotNull ItemStack item) { + ItemMeta meta = item.getItemMeta(); + + if (meta == null) { + return false; + } + + return guiItem.getUUID().equals(meta.getPersistentDataContainer().get(guiItem.getKey(), UUIDTagType.INSTANCE)); + } + /** * Finds a type of {@link GuiItem} from the provided collection of items based on the provided {@link ItemStack}. * The items will be compared using internal data. When the item does not have this data, this method will return @@ -548,19 +718,13 @@ public static void load(@NotNull Pane pane, @NotNull Object instance, @NotNull E @Nullable @Contract(pure = true) protected static T findMatchingItem(@NotNull Collection items, @NotNull ItemStack item) { - ItemMeta meta = item.getItemMeta(); - if (meta == null) { - return null; - } - - UUID uuid = meta.getPersistentDataContainer().get(GuiItem.KEY_UUID, UUIDTagType.INSTANCE); - if (uuid == null) { - return null; + for (T guiItem : items) { + if (matchesItem(guiItem, item)) { + return guiItem; + } } - return items.stream() - .filter(guiItem -> guiItem.getUUID().equals(uuid)) - .findAny().orElse(null); + return null; } /** @@ -615,7 +779,6 @@ public void setOnClick(@Nullable Consumer onClick) { /** * Calls the consumer (if it's not null) that was specified using {@link #setOnClick(Consumer)}, * so the consumer that should be called whenever this pane is clicked in. - * Catches and logs all exceptions the consumer might throw. * * @param event the event to handle * @since 0.6.0 @@ -624,17 +787,93 @@ protected void callOnClick(@NotNull InventoryClickEvent event) { if (onClick == null) { return; } - + + try { onClick.accept(event); } catch (Throwable t) { - Logger logger = JavaPlugin.getProvidingPlugin(getClass()).getLogger(); - logger.log(Level.SEVERE, "Exception while handling click event in inventory '" - + event.getView().getTitle() + "', slot=" + event.getSlot() + ", for " - + getClass().getSimpleName() + ", x=" + x + ", y=" + y + ", length=" + length + ", height=" + height, t); + throw new RuntimeException( + "Exception while handling click event in inventory '" + + InventoryViewUtil.getInstance().getTitle(event.getView()) + "', slot=" + event.getSlot() + + ", for " + getClass().getSimpleName() + ", x=" + getX() + ", y=" + getY() + + ", length=" + length + ", height=" + height, + t + ); } } + /** + * Creates a pane which displays as a border around the outside of the pane consisting of the provided item. The + * slot, length and height parameters are used for the respective properties of the pane. If either the length or + * height is negative an {@link IllegalArgumentException} will be thrown. + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param item the item of which the border is made + * @return the created pane which displays a border + * @since 0.10.8 + * @throws IllegalArgumentException if length or height is negative + */ + @NotNull + @Contract(pure = true) + public static Pane createBorder(Slot slot, int length, int height, @NotNull GuiItem item) { + if (length < 0) { + throw new IllegalArgumentException("Length should be non-negative"); + } + + if (height < 0) { + throw new IllegalArgumentException("Height should be non-negative"); + } + + String[] mask = new String[height]; + + if (height > 0) { + mask[0] = createLine(length); + } + + if (height > 1) { + mask[height - 1] = createLine(length); + } + + for (int yIndex = 1; yIndex < height - 1; yIndex++) { + StringBuilder builder = new StringBuilder("1"); + + for (int i = 0; i < length - 2; i++) { + builder.append('0'); + } + + mask[yIndex] = builder.append('1').toString(); + } + + OutlinePane pane = new OutlinePane(slot, length, height); + pane.applyMask(new Mask(mask)); + pane.addItem(item); + pane.setRepeat(true); + + return pane; + } + + /** + * Creates a pane which displays as a border around the outside of the pane consisting of the provided item. The x, + * y, length and height parameters are used for the respective properties of the pane. If either the length or + * height is negative an {@link IllegalArgumentException} will be thrown. + * + * @param x the x coordinate of the pane + * @param y the y coordinate of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param item the item of which the border is made + * @return the created pane which displays a border + * @since 0.10.7 + * @throws IllegalArgumentException if length or height is negative + */ + @NotNull + @Contract(pure = true) + public static Pane createBorder(int x, int y, int length, int height, @NotNull GuiItem item) { + return createBorder(Slot.fromXY(x, y), length, height, item); + } + /** * Registers a property that can be used inside an XML file to add additional new properties. * The use of {@link Gui#registerProperty(String, Function)} is preferred over this method. @@ -653,6 +892,31 @@ public static void registerProperty(@NotNull String attributeName, @NotNull Func PROPERTY_MAPPINGS.put(attributeName, function); } + /** + * Creates a string containing the character '1' repeated length amount of times. If the provided length is negative + * an {@link IllegalArgumentException} will be thrown. + * + * @param length the length of the string + * @return the string containing '1's + * @since 0.10.7 + * @throws IllegalArgumentException if length is negative + */ + @NotNull + @Contract(pure = true) + private static String createLine(int length) { + if (length < 0) { + throw new IllegalArgumentException("Length should be non-negative"); + } + + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < length; i++) { + builder.append('1'); + } + + return builder.toString(); + } + /** * An enum representing the rendering priorities for the panes. Uses a similar system to Bukkit's * {@link org.bukkit.event.EventPriority} system diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java index 4adde932e..91a561f50 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/PatternPane.java @@ -5,9 +5,12 @@ import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import com.github.stefvanschie.inventoryframework.util.GeometryUtil; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -54,27 +57,42 @@ public class PatternPane extends Pane implements Flippable, Rotatable { /** * Constructs a new pattern pane. * - * @param x the upper left x coordinate of the pane - * @param y the upper left y coordinate of the pane + * @param slot the slot of the pane * @param length the length of the pane * @param height the height of the pane * @param priority the priority of the pane * @param pattern the pattern of the pane * @throws IllegalArgumentException when the pane and pattern dimensions don't match - * @since 0.9.8 + * @since 0.10.8 */ - public PatternPane(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Pattern pattern) { - super(x, y, length, height, priority); + public PatternPane(@NotNull Slot slot, int length, int height, @NotNull Priority priority, @NotNull Pattern pattern) { + super(slot, length, height, priority); if (pattern.getLength() != length || pattern.getHeight() != height) { throw new IllegalArgumentException( - "Dimensions of the provided pattern do not match the dimensions of the pane" + "Dimensions of the provided pattern do not match the dimensions of the pane" ); } this.pattern = pattern; } + /** + * Constructs a new pattern pane. + * + * @param x the upper left x coordinate of the pane + * @param y the upper left y coordinate of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @param pattern the pattern of the pane + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.9.8 + */ + public PatternPane(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Pattern pattern) { + this(Slot.fromXY(x, y), length, height, priority, pattern); + } + /** * Constructs a new pattern pane, with no position. * @@ -88,6 +106,20 @@ public PatternPane(int length, int height, @NotNull Pattern pattern) { this(0, 0, length, height, pattern); } + /** + * Constructs a new pattern pane. + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param pattern the pattern of the pane + * @throws IllegalArgumentException when the pane and pattern dimensions don't match + * @since 0.10.8 + */ + public PatternPane(@NotNull Slot slot, int length, int height, @NotNull Pattern pattern) { + this(slot, length, height, Priority.NORMAL, pattern); + } + /** * Constructs a new pattern pane. * @@ -133,8 +165,10 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs newX = coordinates.getKey(); newY = coordinates.getValue(); - int finalRow = getY() + newY + paneOffsetY; - int finalColumn = getX() + newX + paneOffsetX; + Slot slot = getSlot(); + + int finalRow = slot.getY(maxLength) + newY + paneOffsetY; + int finalColumn = slot.getX(maxLength) + newX + paneOffsetX; inventoryComponent.setItem(item, finalColumn, finalRow); } @@ -148,10 +182,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { @@ -181,16 +222,16 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Contract(pure = true) @Override public PatternPane copy() { - PatternPane patternPane = new PatternPane(getX(), getY(), getLength(), getHeight(), getPriority(), getPattern()); + PatternPane patternPane = new PatternPane(getSlot(), getLength(), getHeight(), getPriority(), getPattern()); patternPane.setVisible(isVisible()); patternPane.onClick = onClick; patternPane.uuid = uuid; - patternPane.setRotation(getRotation()); - patternPane.flipHorizontally(isFlippedHorizontally()); - patternPane.flipVertically(isFlippedVertically()); + patternPane.rotation = rotation; + patternPane.flippedHorizontally = flippedHorizontally; + patternPane.flippedVertically = flippedVertically; return patternPane; } @@ -339,87 +380,119 @@ public int getRotation() { * Loads a pattern pane from a given element * * @param instance the instance class - * @param element the element + * @param element the element + * @param plugin the plugin that will own the underlying items * @return the pattern pane + * @since 0.10.8 */ @NotNull - public static PatternPane load(@NotNull Object instance, @NotNull Element element) { - try { - NodeList childNodes = element.getChildNodes(); + public static PatternPane load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + NodeList childNodes = element.getChildNodes(); - Pattern pattern = null; - Map bindings = new HashMap<>(); + Pattern pattern = null; + Map bindings = new HashMap<>(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.item(i); - - if (item.getNodeType() != Node.ELEMENT_NODE) { - continue; - } + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); - Element child = (Element) item; - String name = item.getNodeName(); + if (item.getNodeType() != Node.ELEMENT_NODE) { + continue; + } - if (name.equals("pattern")) { - pattern = Pattern.load(child); - } else if (name.equals("binding")) { - String character = child.getAttribute("char"); + Element child = (Element) item; + String name = item.getNodeName(); - if (character == null) { - throw new XMLLoadException("Missing char attribute on binding"); - } + if (name.equals("pattern")) { + pattern = Pattern.load(child); + } else if (name.equals("binding")) { + if (!child.hasAttribute("char")) { + throw new XMLLoadException("Tag binding is missing char attribute"); + } - if (character.codePointCount(0, character.length()) != 1) { - throw new XMLLoadException("Char attribute doesn't have one character"); - } + String character = child.getAttribute("char"); - NodeList children = child.getChildNodes(); - GuiItem guiItem = null; + if (character.codePointCount(0, character.length()) != 1) { + throw new XMLLoadException("Char attribute doesn't have one character"); + } - for (int index = 0; index < children.getLength(); index++) { - Node guiItemNode = children.item(index); + NodeList children = child.getChildNodes(); + GuiItem guiItem = null; - if (guiItemNode.getNodeType() != Node.ELEMENT_NODE) { - continue; - } + for (int index = 0; index < children.getLength(); index++) { + Node guiItemNode = children.item(index); - if (guiItem != null) { - throw new XMLLoadException("Binding has multiple inner tags, one expected"); - } + if (guiItemNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } - guiItem = Pane.loadItem(instance, (Element) guiItemNode); + if (guiItem != null) { + throw new XMLLoadException("Binding has multiple inner tags, one expected"); } - //guaranteed to only be a single code point - bindings.put(character.codePoints().toArray()[0], guiItem); - } else { - throw new XMLLoadException("Unknown tag " + name + " in pattern pane"); + guiItem = Pane.loadItem(instance, (Element) guiItemNode, plugin); } - } - if (pattern == null) { - throw new XMLLoadException("Pattern pane doesn't have a pattern"); + //guaranteed to only be a single code point + bindings.put(character.codePoints().toArray()[0], guiItem); + } else { + throw new XMLLoadException("Unknown tag " + name + " in pattern pane"); } + } - PatternPane patternPane = new PatternPane( - Integer.parseInt(element.getAttribute("length")), - Integer.parseInt(element.getAttribute("height")), - pattern - ); + if (pattern == null) { + throw new XMLLoadException("Pattern pane doesn't have a pattern"); + } - Pane.load(patternPane, instance, element); - Flippable.load(patternPane, element); - Rotatable.load(patternPane, element); + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Pattern pane XML tag does not have the mandatory length attribute"); + } - if (!element.hasAttribute("populate")) { - for (Map.Entry entry : bindings.entrySet()) { - patternPane.bindItem(entry.getKey(), entry.getValue()); - } - } + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Pattern pane XML tag does not have the mandatory height attribute"); + } + + int length; + int height; + + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } - return patternPane; + try { + height = Integer.parseInt(element.getAttribute("height")); } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Height attribute is not an integer", exception); + } + + PatternPane patternPane = new PatternPane(length, height, pattern); + + Pane.load(patternPane, instance, element); + Flippable.load(patternPane, element); + Rotatable.load(patternPane, element); + + if (!element.hasAttribute("populate")) { + for (Map.Entry entry : bindings.entrySet()) { + patternPane.bindItem(entry.getKey(), entry.getValue()); + } } + + return patternPane; + } + + /** + * Loads a pattern pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the pattern pane + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static PatternPane load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(PatternPane.class)); } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Rotatable.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Rotatable.java index 8905323c3..630946a7d 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Rotatable.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/Rotatable.java @@ -1,5 +1,6 @@ package com.github.stefvanschie.inventoryframework.pane; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -45,7 +46,11 @@ public interface Rotatable { */ static void load(@NotNull Rotatable rotatable, @NotNull Element element) { if (element.hasAttribute("rotation")) { - rotatable.setRotation(Integer.parseInt(element.getAttribute("rotation"))); + try { + rotatable.setRotation(Integer.parseInt(element.getAttribute("rotation"))); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Rotation attribute is not an integer", exception); + } } } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/StaticPane.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/StaticPane.java index b77249f35..88cb5fb6e 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/StaticPane.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/StaticPane.java @@ -4,9 +4,12 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import com.github.stefvanschie.inventoryframework.util.GeometryUtil; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,6 +22,12 @@ /** * A pane for static items and stuff. All items will have to be specified a slot, or will be added in the next position. + *

+ * This pane allows you to specify the positions of the items either in the form of an x and y coordinate pair or as an + * index, in which case the indexing starts from the top left and continues to the right and bottom, with the horizontal + * axis taking priority. There are nuances at play with regard to mixing these two types of positioning systems within + * the same pane. It's recommended to only use one of these systems per pane and to not mix them. + *

*/ public class StaticPane extends Pane implements Flippable, Rotatable { @@ -27,7 +36,7 @@ public class StaticPane extends Pane implements Flippable, Rotatable { * the key and the y coordinate is the value. */ @NotNull - private final Map, GuiItem> items; + private final Map items; /** * The clockwise rotation of this pane in degrees @@ -39,12 +48,37 @@ public class StaticPane extends Pane implements Flippable, Rotatable { */ private boolean flipHorizontally, flipVertically; - public StaticPane(int x, int y, int length, int height, @NotNull Priority priority) { - super(x, y, length, height, priority); + /** + * Creates a new static pane. + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @param priority the priority of the pane + * @since 0.10.8 + */ + public StaticPane(Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); this.items = new HashMap<>(length * height); } + public StaticPane(int x, int y, int length, int height, @NotNull Priority priority) { + this(Slot.fromXY(x, y), length, height, priority); + } + + /** + * Creates a new static pane. + * + * @param slot the slot of the pane + * @param length the length of the pane + * @param height the height of the pane + * @since 0.10.8 + */ + public StaticPane(Slot slot, int length, int height) { + this(slot, length, height, Priority.NORMAL); + } + public StaticPane(int x, int y, int length, int height) { this(x, y, length, height, Priority.NORMAL); } @@ -53,6 +87,18 @@ public StaticPane(int length, int height) { this(0, 0, length, height); } + /** + * {@inheritDoc} + * + * If there are multiple items in the same position when displaying the items, either one of those items may be + * shown. In particular, there is no guarantee that a specific item will be shown. + * + * @param inventoryComponent {@inheritDoc} + * @param paneOffsetX {@inheritDoc} + * @param paneOffsetY {@inheritDoc} + * @param maxLength {@inheritDoc} + * @param maxHeight {@inheritDoc} + */ @Override public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { @@ -60,9 +106,10 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs int height = Math.min(this.height, maxHeight); items.entrySet().stream().filter(entry -> entry.getValue().isVisible()).forEach(entry -> { - Map.Entry location = entry.getKey(); + Slot location = entry.getKey(); - int x = location.getKey(), y = location.getValue(); + int x = location.getX(getLength()); + int y = location.getY(getLength()); if (flipHorizontally) x = length - x - 1; @@ -82,27 +129,41 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs GuiItem item = entry.getValue(); - int finalRow = getY() + y + paneOffsetY; - int finalColumn = getX() + x + paneOffsetX; + Slot slot = getSlot(); + int finalRow = slot.getY(maxLength) + y + paneOffsetY; + int finalColumn = slot.getX(maxLength) + x + paneOffsetX; inventoryComponent.setItem(item, finalColumn, finalRow); }); } /** - * Adds a gui item at the specific spot in the pane. If the coordinates as specified by the x and y parameters is - * already occupied, that item will be replaced by the item parameter. + * Adds a gui item at the specific spot in the pane. If there is another item specified in terms of x and y + * coordinates that are equal to the coordinates of this item, the old item will be overwritten by this item. * * @param item the item to set * @param x the x coordinate of the position of the item * @param y the y coordinate of the position of the item */ public void addItem(@NotNull GuiItem item, int x, int y) { - items.keySet().removeIf(entry -> entry.getKey() == x && entry.getValue() == y); - - items.put(new AbstractMap.SimpleEntry<>(x, y), item); + addItem(item, Slot.fromXY(x, y)); } + /** + * Adds a gui item at the specific spot in the pane. If the slot is specified in terms of an x and y coordinate pair + * and this pane contains another item whose position is specified as such and these positions are equal, the old + * item will be overwritten by this item. If the slot is specified in terms of an index and this pane contains + * another item whose position is specified as such and these positions are equal, the old item will be overwritten + * by this item. + * + * @param item the item to set + * @param slot the position of the item + * @since 0.10.8 + */ + public void addItem(@NotNull GuiItem item, Slot slot) { + this.items.put(slot, item); + } + /** * Removes the specified item from the pane * @@ -113,6 +174,29 @@ public void removeItem(@NotNull GuiItem item) { items.values().removeIf(guiItem -> guiItem.equals(item)); } + /** + * Removes the specified item from the pane. This will only remove items whose slot was specified in terms of an x + * and y coordinate pair which matches the coordinate specified. + * + * @param x the x coordinate of the item to remove + * @param y the y coordinate of the item to remove + * @since 0.10.0 + */ + public void removeItem(int x, int y) { + this.items.remove(Slot.fromXY(x, y)); + } + + /** + * Removes the specified item from the pane. This will only remove items whose slot was specified in the same way as + * the original slot and whose slot positions match. + * + * @param slot the slot of the item to remove + * @since 0.10.8 + */ + public void removeItem(@NotNull Slot slot) { + this.items.remove(slot); + } + @Override public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComponent, @NotNull InventoryClickEvent event, int slot, int paneOffsetX, int paneOffsetY, int maxLength, @@ -120,10 +204,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { @@ -153,12 +244,10 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Contract(pure = true) @Override public StaticPane copy() { - StaticPane staticPane = new StaticPane(x, y, length, height, getPriority()); + StaticPane staticPane = new StaticPane(getSlot(), length, height, getPriority()); - for (Map.Entry, GuiItem> entry : items.entrySet()) { - Map.Entry coordinates = entry.getKey(); - - staticPane.addItem(entry.getValue().copy(), coordinates.getKey(), coordinates.getValue()); + for (Map.Entry entry : items.entrySet()) { + staticPane.addItem(entry.getValue().copy(), entry.getKey()); } staticPane.setVisible(isVisible()); @@ -189,31 +278,45 @@ public void setRotation(int rotation) { * Fills all empty space in the pane with the given {@code itemStack} and adds the given action * * @param itemStack The {@link ItemStack} to fill the empty space with - * @param action The action called whenever an interaction with the item happens - * @since 0.5.9 + * @param action The action called whenever an interaction with the item happens + * @param plugin the plugin that will be the owner of the created items + * @see #fillWith(ItemStack, Consumer) + * @since 0.10.8 */ - public void fillWith(@NotNull ItemStack itemStack, @Nullable Consumer action) { + public void fillWith(@NotNull ItemStack itemStack, @Nullable Consumer action, + @NotNull Plugin plugin) { //The non empty spots - Set> locations = this.items.keySet(); + Set locations = this.items.keySet(); for (int y = 0; y < this.getHeight(); y++) { for (int x = 0; x < this.getLength(); x++) { boolean found = false; - for (Map.Entry location : locations) { - if (location.getKey() == x && location.getValue() == y) { + for (Slot location : locations) { + if (location.getX(getLength()) == x && location.getY(getLength()) == y) { found = true; break; } } if (!found) { - this.addItem(new GuiItem(itemStack, action), x, y); + this.addItem(new GuiItem(itemStack, action, plugin), x, y); } } } } + /** + * Fills all empty space in the pane with the given {@code itemStack} and adds the given action + * + * @param itemStack The {@link ItemStack} to fill the empty space with + * @param action The action called whenever an interaction with the item happens + * @since 0.5.9 + */ + public void fillWith(@NotNull ItemStack itemStack, @Nullable Consumer action) { + fillWith(itemStack, action, JavaPlugin.getProvidingPlugin(StaticPane.class)); + } + /** * Fills all empty space in the pane with the given {@code itemStack} * @@ -225,12 +328,55 @@ public void fillWith(@NotNull ItemStack itemStack) { this.fillWith(itemStack, null); } + /** + * Gets the item located at the provided slot. If the provided slot is empty, this will return null. The slots are + * checked based on their position without regard for their underlying definition. For example, if an item was added + * with its slot specified as an x,y coordinate pair, but this method is invoked with a slot specified as an index, + * this item may still be returned if the x,y coordinate pair and slot are at the same position, given the current + * dimensions of the pane. If multiple items match the position indicated by the provided slot, any of those items + * may be the result of this invocation. + * + * @param slot the slot of the item + * @return the item at this position, or null if there is no such item + * @since 0.11.4 + */ + @Nullable + @Contract(pure = true) + public GuiItem getItem(@NotNull Slot slot) { + int x = slot.getX(getLength()); + int y = slot.getY(getLength()); + + for (Map.Entry entry : this.items.entrySet()) { + Slot key = entry.getKey(); + + if (key.getX(getLength()) == x && key.getY(getLength()) == y) { + return entry.getValue(); + } + } + + return null; + } + @NotNull @Override public Collection getItems() { return items.values(); } + /** + * Gets all items by their corresponding slots. The slots correspond to the type they were added with. For example, + * if the slot was specified as an x,y coordinate pair, the slot will also be specified as such a pair. The returned + * map is unmodifiable. + * + * @return a map of all items by their slot + * @since 0.11.4 + */ + @NotNull + @Contract(pure = true) + public Map<@NotNull Slot, @NotNull GuiItem> getSlottedItems() { + return Collections.unmodifiableMap(this.items); + } + @Override public void clear() { items.clear(); @@ -275,41 +421,73 @@ public boolean isFlippedVertically() { * Loads an outline pane from a given element * * @param instance the instance class - * @param element the element + * @param element the element + * @param plugin the plugin that will be the owner of the udnerlying items * @return the outline pane + * @since 0.10.8 */ @NotNull - public static StaticPane load(@NotNull Object instance, @NotNull Element element) { - try { - StaticPane staticPane = new StaticPane( - Integer.parseInt(element.getAttribute("length")), - Integer.parseInt(element.getAttribute("height")) - ); + public static StaticPane load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Cycle button XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Cycle button XML tag does not have the mandatory height attribute"); + } + + int length; + int height; - Pane.load(staticPane, instance, element); - Flippable.load(staticPane, element); - Rotatable.load(staticPane, element); + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } - if (element.hasAttribute("populate")) - return staticPane; + try { + height = Integer.parseInt(element.getAttribute("height")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Height attribute is not an integer", exception); + } - NodeList childNodes = element.getChildNodes(); + StaticPane staticPane = new StaticPane(length, height); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.item(i); + Pane.load(staticPane, instance, element); + Flippable.load(staticPane, element); + Rotatable.load(staticPane, element); - if (item.getNodeType() != Node.ELEMENT_NODE) - continue; + if (element.hasAttribute("populate")) + return staticPane; - Element child = (Element) item; + NodeList childNodes = element.getChildNodes(); - staticPane.addItem(Pane.loadItem(instance, child), Integer.parseInt(child.getAttribute("x")), - Integer.parseInt(child.getAttribute("y"))); - } + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); - return staticPane; - } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); - } + if (item.getNodeType() != Node.ELEMENT_NODE) + continue; + + Element child = (Element) item; + + staticPane.addItem(Pane.loadItem(instance, child, plugin), Slot.deserialize(child)); + } + + return staticPane; } + + /** + * Loads an outline pane from a given element + * + * @param instance the instance class + * @param element the element + * @return the outline pane + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static StaticPane load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(StaticPane.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/CycleButton.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/CycleButton.java index 709d70284..b7e673eec 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/CycleButton.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/CycleButton.java @@ -5,7 +5,10 @@ import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.pane.Pane; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -32,10 +35,35 @@ public class CycleButton extends Pane { */ private int position = 0; + /** + * Creates a new cycle button + * + * @param slot the slot of the button + * @param length the length of the button + * @param height the height of the button + * @param priority the priority of the button + * @since 0.10.8 + */ + public CycleButton(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); + } + public CycleButton(int x, int y, int length, int height, @NotNull Priority priority) { super(x, y, length, height, priority); } + /** + * Creates a new cycle button + * + * @param slot the slot of the button + * @param length the length of the button + * @param height the height of the button + * @since 0.10.8 + */ + public CycleButton(@NotNull Slot slot, int length, int height) { + super(slot, length, height); + } + public CycleButton(int x, int y, int length, int height) { super(x, y, length, height); } @@ -51,16 +79,25 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { return false; } + int previousPosition = position; + position++; if (position == panes.size()) { @@ -69,7 +106,8 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp callOnClick(event); - Pane pane = panes.get(position); + //use the previous position, since that will have the pane we clicked on + Pane pane = panes.get(previousPosition); pane.click(gui, inventoryComponent, event, slot, paneOffsetX + x, paneOffsetY + y, length, height); @@ -81,8 +119,10 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp @Override public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { - int newX = paneOffsetX + x; - int newY = paneOffsetY + y; + Slot slot = getSlot(); + + int newX = paneOffsetX + slot.getX(maxLength); + int newY = paneOffsetY + slot.getY(maxLength); int newMaxLength = Math.min(maxLength, length); int newMaxHeight = Math.min(maxHeight, height); @@ -94,7 +134,7 @@ public void display(@NotNull InventoryComponent inventoryComponent, int paneOffs @Contract(pure = true) @Override public CycleButton copy() { - CycleButton cycleButton = new CycleButton(x, y, length, height, getPriority()); + CycleButton cycleButton = new CycleButton(getSlot(), length, height, getPriority()); for (Pane pane : panes) { cycleButton.addPane(pane); @@ -162,19 +202,33 @@ public void cycle() { * * @param instance the instance class * @param element the element + * @param plugin the plugin that will be the owner of the underlying items * @return the cycle button - * @since 0.5.0 + * @since 0.10.8 */ @NotNull - public static CycleButton load(@NotNull Object instance, @NotNull Element element) { + public static CycleButton load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Cycle button XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Cycle button XML tag does not have the mandatory height attribute"); + } + int length; int height; try { length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { height = Integer.parseInt(element.getAttribute("height")); } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Height attribute is not an integer", exception); } CycleButton cycleButton = new CycleButton(length, height); @@ -194,9 +248,25 @@ public static CycleButton load(@NotNull Object instance, @NotNull Element elemen continue; } - cycleButton.addPane(Gui.loadPane(instance, pane)); + cycleButton.addPane(Gui.loadPane(instance, pane, plugin)); } return cycleButton; } + + /** + * Loads a cycle button from a given element + * + * @param instance the instance class + * @param element the element + * @return the cycle button + * @since 0.5.0 + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Deprecated + public static CycleButton load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(CycleButton.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Label.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Label.java index 205315ad2..6e724e534 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Label.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Label.java @@ -6,12 +6,17 @@ import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import com.github.stefvanschie.inventoryframework.font.util.Font; import com.github.stefvanschie.inventoryframework.pane.*; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; +import java.util.function.BiFunction; + /** * A label for displaying text. * @@ -31,6 +36,113 @@ public class Label extends OutlinePane { @NotNull private String text; + /** + * The plugin to be sed for creating items + */ + @NotNull + private final Plugin plugin; + + /** + * Creates a new label + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @param font the character set + * @param plugin the plugin that will be the owner for this label's items + * @see #Label(int, int, int, int, Priority, Font) + * @since 0.10.8 + */ + public Label(@NotNull Slot slot, int length, int height, @NotNull Priority priority, @NotNull Font font, + @NotNull Plugin plugin) { + super(slot, length, height); + + this.font = font; + this.text = ""; + + this.plugin = plugin; + + setPriority(priority); + } + + /** + * Creates a new label + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param priority the priority + * @param font the character set + * @param plugin the plugin that will be the owner for this label's items + * @see #Label(int, int, int, int, Priority, Font) + * @since 0.10.8 + */ + public Label(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Font font, + @NotNull Plugin plugin) { + this(Slot.fromXY(x, y), length, height, priority, font, plugin); + } + + /** + * Creates a new label + * + * @param slot the slot + * @param length the length + * @param height the height + * @param font the character set + * @param plugin the plugin that will be the owner for this label's items + * @see #Label(int, int, int, int, Font) + * @since 0.10.8 + */ + public Label(@NotNull Slot slot, int length, int height, @NotNull Font font, @NotNull Plugin plugin) { + this(slot, length, height, Priority.NORMAL, font, plugin); + } + + /** + * Creates a new label + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param font the character set + * @param plugin the plugin that will be the owner for this label's items + * @see #Label(int, int, int, int, Font) + * @since 0.10.8 + */ + public Label(int x, int y, int length, int height, @NotNull Font font, @NotNull Plugin plugin) { + this(x, y, length, height, Priority.NORMAL, font, plugin); + } + + /** + * Creates a new label + * + * @param length the length + * @param height the height + * @param font the character set + * @param plugin the plugin that will be the owner for this label's items + * @see #Label(int, int, Font) + * @since 0.10.8 + */ + public Label(int length, int height, @NotNull Font font, @NotNull Plugin plugin) { + this(0, 0, length, height, font, plugin); + } + + /** + * Creates a new label + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @param font the character set + * @since 0.10.8 + */ + public Label(@NotNull Slot slot, int length, int height, @NotNull Priority priority, @NotNull Font font) { + this(slot, length, height, priority, font, JavaPlugin.getProvidingPlugin(Label.class)); + } + /** * Creates a new label * @@ -43,9 +155,20 @@ public class Label extends OutlinePane { * @since 0.5.0 */ public Label(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Font font) { - this(x, y, length, height, font); + this(x, y, length, height, priority, font, JavaPlugin.getProvidingPlugin(Label.class)); + } - setPriority(priority); + /** + * Creates a new label + * + * @param slot the slot + * @param length the length + * @param height the height + * @param font the character set + * @since 0.10.8 + */ + public Label(@NotNull Slot slot, int length, int height, @NotNull Font font) { + this(slot, length, height, Priority.NORMAL, font); } /** @@ -59,10 +182,7 @@ public Label(int x, int y, int length, int height, @NotNull Priority priority, @ * @since 0.5.0 */ public Label(int x, int y, int length, int height, @NotNull Font font) { - this(length, height, font); - - this.x = x; - this.y = y; + this(x, y, length, height, Priority.NORMAL, font); } /** @@ -74,19 +194,22 @@ public Label(int x, int y, int length, int height, @NotNull Font font) { * @since 0.5.0 */ public Label(int length, int height, @NotNull Font font) { - super(length, height); - - this.font = font; - this.text = ""; + this(0, 0, length, height, font); } /** - * Sets the text to be displayed in this label + * Sets the text to be displayed in this label. If this label already had text, this text will be overwritten. The + * specified processor will be called for each character that is part of the specified text. The provided character + * will be the original character that was attempted to be shown - it is not subject to any transformations that may + * be applied for finding a valid item corresponding to this character, such as capitalization changes. * * @param text the new text - * @since 0.5.0 + * @param processor processes each character before using them + * @since 0.10.4 */ - public void setText(@NotNull String text) { + public void setText(@NotNull String text, + @NotNull BiFunction processor) { this.text = text; clear(); @@ -106,15 +229,26 @@ public void setText(@NotNull String text) { item = font.getDefaultItem(); } - addItem(new GuiItem(item)); + addItem(processor.apply(character, item.clone())); } } + /** + * Sets the text to be displayed in this label. If this label already had text, this text will be overwritten. + * + * @param text the new text + * @see #setText(String, BiFunction) + * @since 0.5.0 + */ + public void setText(@NotNull String text) { + setText(text, (character, item) -> new GuiItem(item, this.plugin)); + } + @NotNull @Contract(pure = true) @Override public Label copy() { - Label label = new Label(x, y, length, height, getPriority(), font); + Label label = new Label(getSlot(), length, height, getPriority(), font, this.plugin); for (GuiItem item : getItems()) { label.addItem(item.copy()); @@ -174,20 +308,35 @@ public Font getFont() { * Loads a label from a given element * * @param instance the instance class - * @param element the element + * @param element the element + * @param plugin the plugin that will be the owner of the underlying items * @return the percentage bar + * @since 0.10.8 */ @NotNull @Contract(pure = true) - public static Label load(@NotNull Object instance, @NotNull Element element) { + public static Label load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Label XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Label XML tag does not have the mandatory height attribute"); + } + int length; int height; try { length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { height = Integer.parseInt(element.getAttribute("height")); } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Height attribute is not an integer", exception); } Font font = null; @@ -200,7 +349,7 @@ public static Label load(@NotNull Object instance, @NotNull Element element) { throw new XMLLoadException("Incorrect font specified for label"); } - Label label = new Label(length, height, font); + Label label = new Label(length, height, font, plugin); Pane.load(label, instance, element); Orientable.load(label, element); @@ -217,4 +366,20 @@ public static Label load(@NotNull Object instance, @NotNull Element element) { return label; } + + /** + * Loads a label from a given element + * + * @param instance the instance class + * @param element the element + * @return the percentage bar + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Contract(pure = true) + @Deprecated + public static Label load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(Label.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PagingButtons.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PagingButtons.java new file mode 100644 index 000000000..e0499623d --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PagingButtons.java @@ -0,0 +1,419 @@ +package com.github.stefvanschie.inventoryframework.pane.component; + +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.pane.PaginatedPane; +import com.github.stefvanschie.inventoryframework.pane.Pane; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Element; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +/** + * An interface for interacting with {@link PaginatedPane}s. This gives two buttons for navigating backwards and + * forwards through the pages of the {@link PaginatedPane}. The backward button will be displayed at (0, 0) of this pane + * and the forward button will be displayed at (length - 1, 0) of this pane. If the paginated pane is at the first page + * or the last page, the backwards respectively the forward button will not show. This does not display the + * {@link PaginatedPane} itself, but is merely an interface for interacting with it. + * + * @since 0.10.14 + */ +public class PagingButtons extends Pane { + + /** + * The paginated pane. + */ + @NotNull + private final PaginatedPane pages; + + /** + * The backwards button. + */ + @NotNull + private GuiItem backwardButton; + + /** + * The forwards button. + */ + @NotNull + private GuiItem forwardButton; + + /** + * Whether to keep the backward/forward button always visible + */ + private boolean keepButtonsVisible; + + /** + * The plugin with which the items were created. + */ + @NotNull + private final Plugin plugin; + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param slot the position of this interface + * @param length the length of this interface + * @param priority the priority of this interface + * @param pages the pages to interact with + * @param plugin the plugin that will be the owner of this interface's items + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons( + @NotNull Slot slot, + int length, + @NotNull Priority priority, + @NotNull PaginatedPane pages, + @NotNull Plugin plugin + ) { + super(slot, length, 1, priority); + + if (length < 2) { + throw new IllegalArgumentException("Length of paging buttons must be at least 2"); + } + + this.pages = pages; + this.plugin = plugin; + + this.backwardButton = new GuiItem(new ItemStack(Material.ARROW), plugin); + this.forwardButton = new GuiItem(new ItemStack(Material.ARROW), plugin); + this.keepButtonsVisible = false; + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param slot the position of this interface + * @param length the length of this interface + * @param priority the priority of this interface + * @param pages the pages to interact with + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(@NotNull Slot slot, int length, @NotNull Priority priority, @NotNull PaginatedPane pages) { + this(slot, length, priority, pages, JavaPlugin.getProvidingPlugin(PagingButtons.class)); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param slot the position of this interface + * @param length the length of this interface + * @param pages the pages to interact with + * @param plugin the plugin that will be the owner of this interface's items + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(@NotNull Slot slot, int length, @NotNull PaginatedPane pages, @NotNull Plugin plugin) { + this(slot, length, Priority.NORMAL, pages, plugin); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param slot the position of this interface + * @param length the length of this interface + * @param pages the pages to interact with + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(@NotNull Slot slot, int length, @NotNull PaginatedPane pages) { + this(slot, length, Priority.NORMAL, pages); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param length the length of this interface + * @param priority the priority of this interface + * @param pages the pages to interact with + * @param plugin the plugin that will be the owner of this interface's items + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(int length, @NotNull Priority priority, @NotNull PaginatedPane pages, @NotNull Plugin plugin) { + this(Slot.fromXY(0, 0), length, priority, pages, plugin); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param length the length of this interface + * @param priority the priority of this interface + * @param pages the pages to interact with + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(int length, @NotNull Priority priority, @NotNull PaginatedPane pages) { + this(Slot.fromXY(0, 0), length, priority, pages, JavaPlugin.getProvidingPlugin(PagingButtons.class)); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param length the length of this interface + * @param pages the pages to interact with + * @param plugin the plugin that will be the owner of this interface's items + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(int length, @NotNull PaginatedPane pages, @NotNull Plugin plugin) { + this(Slot.fromXY(0, 0), length, Priority.NORMAL, pages, plugin); + } + + /** + * Creates a new PagingButtons instance, which controls the provided {@link PaginatedPane}. The backward and forward + * item will be an arrow. If the length provided is less than 2, this will throw an + * {@link IllegalArgumentException}. + * + * @param length the length of this interface + * @param pages the pages to interact with + * @since 0.10.14 + * @throws IllegalArgumentException if the length is less than 2 + */ + public PagingButtons(int length, @NotNull PaginatedPane pages) { + this(Slot.fromXY(0, 0), length, Priority.NORMAL, pages); + } + + @Override + public boolean click( + @NotNull Gui gui, + @NotNull InventoryComponent inventoryComponent, + @NotNull InventoryClickEvent event, + int slot, + int paneOffsetX, + int paneOffsetY, + int maxLength, + int maxHeight + ) { + int length = Math.min(this.length, maxLength); + int height = Math.min(this.height, maxHeight); + + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; + + //this isn't our item + if (x < 0 || x >= length || y < 0 || y >= height) { + return false; + } + + callOnClick(event); + + ItemStack itemStack = event.getCurrentItem(); + + if (itemStack == null) { + return false; + } + + if (matchesItem(this.backwardButton, itemStack)) { + try { + this.pages.setPage(this.pages.getPage() - 1); + + this.backwardButton.callAction(event); + + gui.update(); + } catch (ArrayIndexOutOfBoundsException ignored) {} + + return true; + } + + if (matchesItem(this.forwardButton, itemStack)) { + try { + this.pages.setPage(this.pages.getPage() + 1); + + this.forwardButton.callAction(event); + + gui.update(); + } catch (ArrayIndexOutOfBoundsException ignored) {} + + return true; + } + + return false; + } + + @Override + public void display( + @NotNull InventoryComponent inventoryComponent, + int paneOffsetX, + int paneOffsetY, + int maxLength, + int maxHeight + ) { + int length = Math.min(getLength(), maxLength); + + int x = super.slot.getX(length) + paneOffsetX; + int y = super.slot.getY(length) + paneOffsetY; + + if (this.keepButtonsVisible || this.pages.getPage() > 0) { + inventoryComponent.setItem(this.backwardButton, x, y); + } + + if (this.keepButtonsVisible || this.pages.getPage() < this.pages.getPages() - 1) { + inventoryComponent.setItem(this.forwardButton, x + length - 1, y); + } + } + + /** + * {@inheritDoc} + * + * This does not make a copy of the {@link PaginatedPane} that is being controlled by this interface. + */ + @NotNull + @Contract(pure = true) + @Override + public PagingButtons copy() { + PagingButtons pagingButtons = new PagingButtons(getSlot(), getLength(), getPriority(), this.pages, this.plugin); + + pagingButtons.setVisible(isVisible()); + pagingButtons.onClick = super.onClick; + + pagingButtons.uuid = super.uuid; + + pagingButtons.backwardButton = this.backwardButton.copy(); + pagingButtons.forwardButton = this.forwardButton.copy(); + + return pagingButtons; + } + + @NotNull + @Contract(pure = true) + @Override + public Collection getItems() { + Collection items = new HashSet<>(); + + items.add(this.backwardButton); + items.add(this.forwardButton); + + return Collections.unmodifiableCollection(items); + } + + /** + * Sets the item to be used for navigating backwards. If an event is attached to the item, this event will be called + * after the page has been changed. + * + * @param item the new backward item + * @since 0.10.14 + */ + public void setBackwardButton(@NotNull GuiItem item) { + this.backwardButton = item; + } + + /** + * Sets the item to be used for navigating forwards. If an event is attached to the item, this event will be called + * after the page has been changed. + * + * @param item the new forward item + * @since 0.10.14 + */ + public void setForwardButton(@NotNull GuiItem item) { + this.forwardButton = item; + } + + /** + * Allow to always keep the backward and forward buttons visible when on the first and last page + * + * @param visible Whether to keep the buttons visible + * @since 0.11.6 + */ + public void setButtonsAlwaysVisible(boolean visible) { + this.keepButtonsVisible = visible; + } + + @NotNull + @Contract(pure = true) + @Override + public Collection getPanes() { + return Collections.emptySet(); + } + + /** + * This is a no-op. + * + * @since 0.10.14 + */ + @Override + public void clear() {} + + /** + * Loads a paging buttons pane from an XML element. + * + * @param instance the instance class + * @param element the element + * @param plugin the plugin that will be the owner of the underlying items + * @return the paging buttons pane + * @since 0.10.14 + */ + @NotNull + @Contract(pure = true) + public static PagingButtons load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Paging buttons XML tag does not have the mandatory length attribute"); + } + + int length; + + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + if (!element.hasAttribute("pages")) { + throw new XMLLoadException("Paging buttons does not have pages attribute"); + } + + Element paginatedPaneElement = element.getOwnerDocument().getElementById(element.getAttribute("pages")); + + if (paginatedPaneElement == null) { + throw new XMLLoadException("Paging buttons pages reference is invalid"); + } + + Object paginatedPane = paginatedPaneElement.getUserData("pane"); + + if (!(paginatedPane instanceof PaginatedPane)) { + throw new XMLLoadException("Retrieved data is not a paginated pane"); + } + + PagingButtons pagingButtons = new PagingButtons(length, (PaginatedPane) paginatedPane); + + Pane.load(pagingButtons, instance, element); + + return pagingButtons; + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PercentageBar.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PercentageBar.java index 49340a8cd..3eec42066 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PercentageBar.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/PercentageBar.java @@ -7,7 +7,10 @@ import com.github.stefvanschie.inventoryframework.pane.Orientable; import com.github.stefvanschie.inventoryframework.pane.Pane; import com.github.stefvanschie.inventoryframework.pane.component.util.VariableBar; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -19,10 +22,104 @@ */ public class PercentageBar extends VariableBar { + /** + * Creates a new percentage bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @param plugin the plugin that will be the owner for this percentage bar's items + * @since 0.10.8 + */ + public PercentageBar(@NotNull Slot slot, int length, int height, @NotNull Priority priority, + @NotNull Plugin plugin) { + super(slot, length, height, priority, plugin); + } + + /** + * Creates a new percentage bar + * + * @param x the x coordinate of the bar + * @param y the y coordinate of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @param plugin the plugin that will be the owner for this percentage bar's items + * @since 0.10.8 + */ + public PercentageBar(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Plugin plugin) { + super(x, y, length, height, priority, plugin); + } + + /** + * Creates a new percentage bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this percentage bar's items + * @since 0.10.8 + */ + public PercentageBar(@NotNull Slot slot, int length, int height, @NotNull Plugin plugin) { + super(slot, length, height, plugin); + } + + /** + * Creates a new percentage bar + * + * @param x the x coordinate of the bar + * @param y the y coordinate of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this percentage bar's items + * @since 0.10.8 + */ + public PercentageBar(int x, int y, int length, int height, @NotNull Plugin plugin) { + super(x, y, length, height, plugin); + } + + /** + * Creates a new percentage bar + * + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this percentage bar's items + * @since 0.10.8 + */ + public PercentageBar(int length, int height, @NotNull Plugin plugin) { + super(length, height, plugin); + } + + /** + * Creates a new percentage bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @since 0.10.8 + */ + public PercentageBar(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); + } + public PercentageBar(int x, int y, int length, int height, @NotNull Priority priority) { super(x, y, length, height, priority); } + /** + * Creates a new percentage bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @since 0.10.8 + */ + public PercentageBar(@NotNull Slot slot, int length, int height) { + super(slot, length, height); + } + public PercentageBar(int x, int y, int length, int height) { super(x, y, length, height); } @@ -38,10 +135,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; if (x < 0 || x >= length || y < 0 || y >= height) { return false; @@ -51,8 +155,8 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp event.setCancelled(true); - int newPaneOffsetX = paneOffsetX + getX(); - int newPaneOffsetY = paneOffsetY + getY(); + int newPaneOffsetX = paneOffsetX + xPosition; + int newPaneOffsetY = paneOffsetY + yPosition; return this.fillPane.click( @@ -79,7 +183,7 @@ public void setPercentage(float percentage) { @Contract(pure = true) @Override public PercentageBar copy() { - PercentageBar percentageBar = new PercentageBar(x, y, length, height, getPriority()); + PercentageBar percentageBar = new PercentageBar(getSlot(), length, height, getPriority()); applyContents(percentageBar); @@ -100,23 +204,38 @@ public float getPercentage() { * Loads a percentage bar from a given element * * @param instance the instance class - * @param element the element + * @param element the element + * @param plugin the plugin that will be the owner of the underlying items * @return the percentage bar + * @since 0.10.8 */ @NotNull @Contract(pure = true) - public static PercentageBar load(@NotNull Object instance, @NotNull Element element) { + public static PercentageBar load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Percentage bar XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Percentage bar XML tag does not have the mandatory height attribute"); + } + int length; int height; try { length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { height = Integer.parseInt(element.getAttribute("height")); } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Height attribute is not an integer", exception); } - PercentageBar percentageBar = new PercentageBar(length, height); + PercentageBar percentageBar = new PercentageBar(length, height, plugin); Pane.load(percentageBar, instance, element); Orientable.load(percentageBar, element); @@ -130,10 +249,26 @@ public static PercentageBar load(@NotNull Object instance, @NotNull Element elem try { percentageBar.setPercentage(Float.parseFloat(element.getAttribute("percentage"))); } catch (IllegalArgumentException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Percentage attribute is not a float", exception); } } return percentageBar; } + + /** + * Loads a percentage bar from a given element + * + * @param instance the instance class + * @param element the element + * @return the percentage bar + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Contract(pure = true) + @Deprecated + public static PercentageBar load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(PercentageBar.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Slider.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Slider.java index 17e873ff7..905940f68 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Slider.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/Slider.java @@ -7,7 +7,10 @@ import com.github.stefvanschie.inventoryframework.pane.Orientable; import com.github.stefvanschie.inventoryframework.pane.Pane; import com.github.stefvanschie.inventoryframework.pane.component.util.VariableBar; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Element; @@ -19,10 +22,103 @@ */ public class Slider extends VariableBar { + /** + * Creates a new slider + * + * @param slot the slot of the slider + * @param length the length of the slider + * @param height the height of the slider + * @param priority the priority of the slider + * @param plugin the plugin that will be the owner of the slider's items + * @since 0.10.8 + */ + public Slider(@NotNull Slot slot, int length, int height, @NotNull Priority priority, @NotNull Plugin plugin) { + super(slot, length, height, priority, plugin); + } + + /** + * Creates a new slider + * + * @param x the x coordinate of the slider + * @param y the y coordinate of the slier + * @param length the length of the slider + * @param height the height of the slider + * @param priority the priority of the slider + * @param plugin the plugin that will be the owner of the slider's items + * @since 0.10.8 + */ + public Slider(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Plugin plugin) { + super(x, y, length, height, priority, plugin); + } + + /** + * Creates a new slider + * + * @param slot the slot of the slider + * @param length the length of the slider + * @param height the height of the slider + * @param plugin the plugin that will be the owner of the slider's items + * @since 0.10.8 + */ + public Slider(@NotNull Slot slot, int length, int height, @NotNull Plugin plugin) { + super(slot, length, height, plugin); + } + + /** + * Creates a new slider + * + * @param x the x coordinate of the slider + * @param y the y coordinate of the slier + * @param length the length of the slider + * @param height the height of the slider + * @param plugin the plugin that will be the owner of the slider's items + * @since 0.10.8 + */ + public Slider(int x, int y, int length, int height, @NotNull Plugin plugin) { + super(x, y, length, height, plugin); + } + + /** + * Creates a new slider + * + * @param length the length of the slider + * @param height the height of the slider + * @param plugin the plugin that will be the owner of the slider's items + * @since 0.10.8 + */ + public Slider(int length, int height, @NotNull Plugin plugin) { + super(length, height, plugin); + } + + /** + * Creates a new slider + * + * @param slot the slot of the slider + * @param length the length of the slider + * @param height the height of the slider + * @param priority the priority of the slider + * @since 0.10.8 + */ + public Slider(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + super(slot, length, height, priority); + } + public Slider(int x, int y, int length, int height, @NotNull Priority priority) { super(x, y, length, height, priority); } + /** + * Creates a new slider + * + * @param slot the slot of the slider + * @param length the length of the slider + * @param height the height of the slider + * @since 0.10.8 + */ + public Slider(@NotNull Slot slot, int length, int height) { + super(slot, length, height); + } + public Slider(int x, int y, int length, int height) { super(x, y, length, height); } @@ -38,10 +134,17 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; if (x < 0 || x >= length || y < 0 || y >= height) { return false; @@ -57,8 +160,8 @@ public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComp callOnClick(event); - int newPaneOffsetX = paneOffsetX + getX(); - int newPaneOffsetY = paneOffsetY + getY(); + int newPaneOffsetX = paneOffsetX + xPosition; + int newPaneOffsetY = paneOffsetY + yPosition; boolean success = this.fillPane.click( gui, inventoryComponent, event, slot, newPaneOffsetX, newPaneOffsetY, length, height @@ -88,7 +191,7 @@ public void setValue(float value) { @Contract(pure = true) @Override public Slider copy() { - Slider slider = new Slider(x, y, length, height, getPriority()); + Slider slider = new Slider(getSlot(), length, height, getPriority()); applyContents(slider); @@ -109,23 +212,38 @@ public float getValue() { * Loads a percentage bar from a given element * * @param instance the instance class - * @param element the element + * @param element the element + * @param plugin the plugin that will be the owner of the udnerlying items * @return the percentage bar + * @since 0.10.8 */ @NotNull @Contract(pure = true) - public static Slider load(@NotNull Object instance, @NotNull Element element) { + public static Slider load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Slider XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Slider XML tag does not have the mandatory height attribute"); + } + int length; int height; try { length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { height = Integer.parseInt(element.getAttribute("height")); } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Height attribute is not an integer", exception); } - Slider slider = new Slider(length, height); + Slider slider = new Slider(length, height, plugin); Pane.load(slider, instance, element); Orientable.load(slider, element); @@ -139,10 +257,26 @@ public static Slider load(@NotNull Object instance, @NotNull Element element) { try { slider.setValue(Float.parseFloat(element.getAttribute("value"))); } catch (IllegalArgumentException exception) { - throw new XMLLoadException(exception); + throw new XMLLoadException("Value attribute is not a float", exception); } } return slider; } + + /** + * Loads a percentage bar from a given element + * + * @param instance the instance class + * @param element the element + * @return the percentage bar + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Contract(pure = true) + @Deprecated + public static Slider load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(Slider.class)); + } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/ToggleButton.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/ToggleButton.java index da25e0435..f112147b6 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/ToggleButton.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/ToggleButton.java @@ -1,221 +1,515 @@ -package com.github.stefvanschie.inventoryframework.pane.component; - -import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; -import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; -import com.github.stefvanschie.inventoryframework.gui.GuiItem; -import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; -import com.github.stefvanschie.inventoryframework.pane.OutlinePane; -import com.github.stefvanschie.inventoryframework.pane.Pane; -import org.bukkit.Material; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.w3c.dom.Element; - -import java.util.Collection; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * A button that toggles between an enabled and disabled state. - * - * @since 0.5.0 - */ -public class ToggleButton extends Pane { - - /** - * The panes used for showing the enabled and disabled states - */ - private final OutlinePane enabledPane, disabledPane; - - /** - * Whether the button is enabled or disabled - */ - private boolean enabled = false; - - public ToggleButton(int x, int y, int length, int height, @NotNull Priority priority) { - this(x, y, length, height); - - setPriority(priority); - } - - public ToggleButton(int length, int height) { - super(length, height); - - this.enabledPane = new OutlinePane(0, 0, length, height); - this.enabledPane.addItem(new GuiItem(new ItemStack(Material.GREEN_STAINED_GLASS_PANE))); - this.enabledPane.setRepeat(true); - - this.disabledPane = new OutlinePane(0, 0, length, height); - this.disabledPane.addItem(new GuiItem(new ItemStack(Material.RED_STAINED_GLASS_PANE))); - this.disabledPane.setRepeat(true); - } - - public ToggleButton(int x, int y, int length, int height) { - this(length, height); - - setX(x); - setY(y); - } - - @Override - public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, - int maxHeight) { - int newX = paneOffsetX + x; - int newY = paneOffsetY + y; - - int newMaxLength = Math.min(maxLength, length); - int newMaxHeight = Math.min(maxHeight, height); - - if (enabled) { - enabledPane.display(inventoryComponent, newX, newY, newMaxLength, newMaxHeight); - } else { - disabledPane.display(inventoryComponent, newX, newY, newMaxLength, newMaxHeight); - } - } - - @Override - public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComponent, - @NotNull InventoryClickEvent event, int slot, int paneOffsetX, int paneOffsetY, int maxLength, - int maxHeight) { - int length = Math.min(this.length, maxLength); - int height = Math.min(this.height, maxHeight); - - int adjustedSlot = slot - (getX() + paneOffsetX) - inventoryComponent.getLength() * (getY() + paneOffsetY); - - int x = adjustedSlot % inventoryComponent.getLength(); - int y = adjustedSlot / inventoryComponent.getLength(); - - //this isn't our item - if (x < 0 || x >= length || y < 0 || y >= height) { - return false; - } - - toggle(); - - callOnClick(event); - - int newX = paneOffsetX + x; - int newY = paneOffsetY + y; - - if (enabled) { - enabledPane.click(gui, inventoryComponent, event, slot, newX, newY, length, height); - } else { - disabledPane.click(gui, inventoryComponent, event, slot, newX, newY, length, height); - } - - gui.update(); - - return true; - } - - @NotNull - @Contract(pure = true) - @Override - public ToggleButton copy() { - ToggleButton toggleButton = new ToggleButton(x, y, length, height, getPriority()); - - toggleButton.setVisible(isVisible()); - toggleButton.onClick = onClick; - - toggleButton.uuid = uuid; - - toggleButton.setEnabledItem(enabledPane.getItems().get(0).copy()); - toggleButton.setDisabledItem(disabledPane.getItems().get(0).copy()); - - toggleButton.enabled = enabled; - - return toggleButton; - } - - /** - * Sets the item to use when the button is set to disabled - * - * @param item the disabled item - * @since 0.5.0 - */ - public void setDisabledItem(@NotNull GuiItem item) { - disabledPane.clear(); - - disabledPane.addItem(item); - } - - /** - * Sets the item to use when the button is set to enabled - * - * @param item the enabled item - * @since 0.5.0 - */ - public void setEnabledItem(@NotNull GuiItem item) { - enabledPane.clear(); - - enabledPane.addItem(item); - } - - @NotNull - @Override - public Collection getItems() { - return getPanes().stream().flatMap(pane -> pane.getItems().stream()).collect(Collectors.toSet()); - } - - @NotNull - @Override - public Collection getPanes() { - return Stream.of(enabledPane, disabledPane).collect(Collectors.toSet()); - } - - /** - * Gets whether this toggle button is currently enabled or disabled. - * - * @return whether the button is enabled or disabled - * @since 0.9.6 - */ - @Contract(pure = true) - public boolean isEnabled() { - return enabled; - } - - /** - * Toggles between the enabled and disabled states - * - * @since 0.5.0 - */ - public void toggle() { - enabled = !enabled; - } - - @Override - public void clear() {} - - /** - * Loads a toggle button from an XML element - * - * @param instance the instance class - * @param element the element - * @return the toggle button - * @since 0.5.0 - */ - @NotNull - @Contract(pure = true) - public static ToggleButton load(@NotNull Object instance, @NotNull Element element) { - int length, height; - - try { - length = Integer.parseInt(element.getAttribute("length")); - height = Integer.parseInt(element.getAttribute("height")); - } catch (NumberFormatException exception) { - throw new XMLLoadException(exception); - } - - ToggleButton toggleButton = new ToggleButton(length, height); - - Pane.load(toggleButton, instance, element); - - if (element.hasAttribute("enabled") && Boolean.parseBoolean(element.getAttribute("enabled"))) { - toggleButton.toggle(); - } - - return toggleButton; - } -} +package com.github.stefvanschie.inventoryframework.pane.component; + +import com.github.stefvanschie.inventoryframework.gui.InventoryComponent; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import com.github.stefvanschie.inventoryframework.pane.OutlinePane; +import com.github.stefvanschie.inventoryframework.pane.Pane; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.Element; + +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A button that toggles between an enabled and disabled state. + * + * @since 0.5.0 + */ +public class ToggleButton extends Pane { + + /** + * The panes used for showing the enabled and disabled states + */ + private final OutlinePane enabledPane, disabledPane; + + /** + * Whether the button is enabled or disabled + */ + private boolean enabled; + + /** + * Whether this button can be toggled by a player + */ + private boolean allowToggle = true; + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @param enabled whether the button should start in its enabled or disabled state + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, int, int, Priority, boolean) + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, @NotNull Priority priority, boolean enabled, + @NotNull Plugin plugin) { + super(slot, length, height, priority); + + this.enabled = enabled; + + this.enabledPane = new OutlinePane(length, height); + this.enabledPane.addItem(new GuiItem(new ItemStack(Material.GREEN_STAINED_GLASS_PANE), plugin)); + this.enabledPane.setRepeat(true); + + this.disabledPane = new OutlinePane(length, height); + this.disabledPane.addItem(new GuiItem(new ItemStack(Material.RED_STAINED_GLASS_PANE), plugin)); + this.disabledPane.setRepeat(true); + } + + /** + * Creates a new toggle button + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param priority the priority + * @param enabled whether the button should start in its enabled or disabled state + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, int, int, Priority, boolean) + * @since 0.10.8 + */ + public ToggleButton(int x, int y, int length, int height, @NotNull Priority priority, boolean enabled, + @NotNull Plugin plugin) { + this(Slot.fromXY(x, y), length, height, priority, enabled, plugin); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(Slot, int, int, Priority) + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, @NotNull Priority priority, + @NotNull Plugin plugin) { + this(slot, length, height, priority, false, plugin); + } + + /** + * Creates a new toggle button + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param priority the priority + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, int, int, Priority) + * @since 0.10.8 + */ + public ToggleButton(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Plugin plugin) { + this(x, y, length, height, priority, false, plugin); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param enabled whether the button should start in its enabled or disabled state + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(Slot, int, int, boolean) + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, boolean enabled, @NotNull Plugin plugin) { + this(slot, length, height, Priority.NORMAL, enabled, plugin); + } + + /** + * Creates a new toggle button + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param enabled whether the button should start in its enabled or disabled state + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, int, int, boolean) + * @since 0.10.8 + */ + public ToggleButton(int x, int y, int length, int height, boolean enabled, @NotNull Plugin plugin) { + this(x, y, length, height, Priority.NORMAL, enabled, plugin); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(Slot, int, int) + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, @NotNull Plugin plugin) { + this(slot, length, height, false, plugin); + } + + /** + * Creates a new toggle button + * + * @param x the x coordinate + * @param y the y coordinate + * @param length the length + * @param height the height + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, int, int) + * @since 0.10.8 + */ + public ToggleButton(int x, int y, int length, int height, @NotNull Plugin plugin) { + this(x, y, length, height, false, plugin); + } + + /** + * Creates a new toggle button + * + * @param length the length + * @param height the height + * @param enabled whether the button should start in its enabled or disabled state + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int, boolean) + * @since 0.10.8 + */ + public ToggleButton(int length, int height, boolean enabled, @NotNull Plugin plugin) { + this(0, 0, length, height, enabled); + } + + /** + * Creates a new toggle button + * + * @param length the length + * @param height the height + * @param plugin the plugin that will be the owner of this button's items + * @see #ToggleButton(int, int) + * @since 0.10.8 + */ + public ToggleButton(int length, int height, @NotNull Plugin plugin) { + this(length, height, false); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @param enabled whether the button should start in its enabled or disabled state + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, @NotNull Priority priority, boolean enabled) { + this(slot, length, height, priority, enabled, JavaPlugin.getProvidingPlugin(ToggleButton.class)); + } + + public ToggleButton(int x, int y, int length, int height, @NotNull Priority priority, boolean enabled) { + this(x, y, length, height, priority, enabled, JavaPlugin.getProvidingPlugin(ToggleButton.class)); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param priority the priority + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + this(slot, length, height, priority, false); + } + + public ToggleButton(int x, int y, int length, int height, @NotNull Priority priority) { + this(x, y, length, height, priority, false); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @param enabled whether the button should start in its enabled or disabled state + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height, boolean enabled) { + this(slot, length, height, Priority.NORMAL, enabled); + } + + public ToggleButton(int x, int y, int length, int height, boolean enabled) { + this(x, y, length, height, Priority.NORMAL, enabled); + } + + /** + * Creates a new toggle button + * + * @param slot the slot + * @param length the length + * @param height the height + * @since 0.10.8 + */ + public ToggleButton(@NotNull Slot slot, int length, int height) { + this(slot, length, height, false); + } + + public ToggleButton(int x, int y, int length, int height) { + this(x, y, length, height, false); + } + + public ToggleButton(int length, int height, boolean enabled) { + this(0, 0, length, height, enabled); + } + + public ToggleButton(int length, int height) { + this(length, height, false); + } + + @Override + public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, + int maxHeight) { + int newMaxLength = Math.min(maxLength, length); + int newMaxHeight = Math.min(maxHeight, height); + + int newPaneOffsetX = this.slot.getX(newMaxLength) + paneOffsetX; + int newPaneOffsetY = this.slot.getY(newMaxHeight) + paneOffsetY; + + if (enabled) { + this.enabledPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); + } else { + this.disabledPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); + } + } + + @Override + public boolean click(@NotNull Gui gui, @NotNull InventoryComponent inventoryComponent, + @NotNull InventoryClickEvent event, int slot, int paneOffsetX, int paneOffsetY, int maxLength, + int maxHeight) { + int length = Math.min(this.length, maxLength); + int height = Math.min(this.height, maxHeight); + + Slot paneSlot = getSlot(); + + int xPosition = paneSlot.getX(maxLength); + int yPosition = paneSlot.getY(maxLength); + + int totalLength = inventoryComponent.getLength(); + + int adjustedSlot = slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY); + + int x = adjustedSlot % totalLength; + int y = adjustedSlot / totalLength; + + //this isn't our item + if (x < 0 || x >= length || y < 0 || y >= height) { + return false; + } + + if (this.allowToggle) { + toggle(); + } + + callOnClick(event); + + int newX = paneOffsetX + xPosition; + int newY = paneOffsetY + yPosition; + + /* + Since we've toggled before, the click for the panes should be swapped around. If we haven't toggled due to + allowToggle being false, then we should click the pane corresponding to the current state. An XOR achieves this. + */ + if (enabled == this.allowToggle) { + disabledPane.click(gui, inventoryComponent, event, slot, newX, newY, length, height); + } else { + enabledPane.click(gui, inventoryComponent, event, slot, newX, newY, length, height); + } + + event.setCancelled(true); + + gui.update(); + + return true; + } + + @NotNull + @Contract(pure = true) + @Override + public ToggleButton copy() { + ToggleButton toggleButton = new ToggleButton(getSlot(), length, height, getPriority(), enabled); + + toggleButton.allowToggle = this.allowToggle; + + toggleButton.setVisible(isVisible()); + toggleButton.onClick = onClick; + + toggleButton.uuid = uuid; + + toggleButton.setEnabledItem(enabledPane.getItems().get(0).copy()); + toggleButton.setDisabledItem(disabledPane.getItems().get(0).copy()); + + return toggleButton; + } + + @Override + public void setLength(int length) { + super.setLength(length); + + this.disabledPane.setLength(length); + this.enabledPane.setLength(length); + } + + @Override + public void setHeight(int height) { + super.setHeight(height); + + this.disabledPane.setHeight(height); + this.enabledPane.setHeight(height); + } + + /** + * Sets the item to use when the button is set to disabled + * + * @param item the disabled item + * @since 0.5.0 + */ + public void setDisabledItem(@NotNull GuiItem item) { + disabledPane.clear(); + + disabledPane.addItem(item); + } + + /** + * Sets the item to use when the button is set to enabled + * + * @param item the enabled item + * @since 0.5.0 + */ + public void setEnabledItem(@NotNull GuiItem item) { + enabledPane.clear(); + + enabledPane.addItem(item); + } + + @NotNull + @Override + public Collection getItems() { + return getPanes().stream().flatMap(pane -> pane.getItems().stream()).collect(Collectors.toSet()); + } + + @NotNull + @Override + public Collection getPanes() { + return Stream.of(enabledPane, disabledPane).collect(Collectors.toSet()); + } + + /** + * Sets whether this toggle button can be toggled. This only prevents players from toggling the button and does not + * prevent toggling the button programmatically with methods such as {@link #toggle()}. + * + * @param allowToggle whether this button can be toggled + * @since 0.10.8 + */ + public void allowToggle(boolean allowToggle) { + this.allowToggle = allowToggle; + } + + /** + * Gets whether this toggle button is currently enabled or disabled. + * + * @return whether the button is enabled or disabled + * @since 0.9.6 + */ + @Contract(pure = true) + public boolean isEnabled() { + return enabled; + } + + /** + * Toggles between the enabled and disabled states + * + * @since 0.5.0 + */ + public void toggle() { + enabled = !enabled; + } + + @Override + public void clear() {} + + /** + * Loads a toggle button from an XML element + * + * @param instance the instance class + * @param element the element + * @param plugin the plugin that will be the owner of the underlying items + * @return the toggle button + * @since 0.10.8 + */ + @NotNull + @Contract(pure = true) + public static ToggleButton load(@NotNull Object instance, @NotNull Element element, @NotNull Plugin plugin) { + if (!element.hasAttribute("length")) { + throw new XMLLoadException("Toggle button XML tag does not have the mandatory length attribute"); + } + + if (!element.hasAttribute("height")) { + throw new XMLLoadException("Toggle button XML tag does not have the mandatory height attribute"); + } + + int length; + int height; + + try { + length = Integer.parseInt(element.getAttribute("length")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Length attribute is not an integer", exception); + } + + try { + height = Integer.parseInt(element.getAttribute("height")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("Height attribute is not an integer", exception); + } + + boolean enabled = element.hasAttribute("enabled") && Boolean.parseBoolean(element.getAttribute("enabled")); + ToggleButton toggleButton = new ToggleButton(length, height, enabled, plugin); + + Pane.load(toggleButton, instance, element); + + return toggleButton; + } + + /** + * Loads a toggle button from an XML element + * + * @param instance the instance class + * @param element the element + * @return the toggle button + * @since 0.5.0 + * @deprecated this method is no longer used internally and has been superseded by + * {@link #load(Object, Element, Plugin)} + */ + @NotNull + @Contract(pure = true) + @Deprecated + public static ToggleButton load(@NotNull Object instance, @NotNull Element element) { + return load(instance, element, JavaPlugin.getProvidingPlugin(ToggleButton.class)); + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/util/VariableBar.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/util/VariableBar.java index 1b07921fe..e3a2d0507 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/util/VariableBar.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/component/util/VariableBar.java @@ -6,8 +6,11 @@ import com.github.stefvanschie.inventoryframework.pane.Orientable; import com.github.stefvanschie.inventoryframework.pane.OutlinePane; import com.github.stefvanschie.inventoryframework.pane.Pane; +import com.github.stefvanschie.inventoryframework.pane.util.Slot; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -43,8 +46,33 @@ public abstract class VariableBar extends Pane implements Orientable, Flippable */ protected boolean flipHorizontally, flipVertically; - protected VariableBar(int length, int height) { - super(length, height); + /** + * Creates a new variable bar + * + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this variable bar's items + * @see #VariableBar(int, int) + * @since 0.10.8 + */ + protected VariableBar(int length, int height, @NotNull Plugin plugin) { + this(0, 0, length, height, plugin); + } + + /** + * Creates a new variable bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @param plugin the plugin that will be the owner for this variable bar's items + * @see #VariableBar(Slot, int, int, Priority) + * @since 0.10.8 + */ + protected VariableBar(@NotNull Slot slot, int length, int height, @NotNull Priority priority, + @NotNull Plugin plugin) { + super(slot, length, height); this.value = 0F; this.orientation = Orientation.HORIZONTAL; @@ -53,23 +81,94 @@ protected VariableBar(int length, int height) { this.backgroundPane = new OutlinePane(0, 0, length, height); this.fillPane.addItem(new GuiItem(new ItemStack(Material.GREEN_STAINED_GLASS_PANE), - event -> event.setCancelled(true))); + event -> event.setCancelled(true), plugin)); this.backgroundPane.addItem(new GuiItem(new ItemStack(Material.RED_STAINED_GLASS_PANE), - event -> event.setCancelled(true))); + event -> event.setCancelled(true), plugin)); this.fillPane.setRepeat(true); this.backgroundPane.setRepeat(true); this.fillPane.setVisible(false); + + setPriority(priority); } - protected VariableBar(int x, int y, int length, int height, @NotNull Priority priority) { - this(length, height); + /** + * Creates a new variable bar + * + * @param x the x coordinate of the bar + * @param y the y coordinate of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @param plugin the plugin that will be the owner for this variable bar's items + * @see #VariableBar(int, int) + * @since 0.10.8 + */ + protected VariableBar(int x, int y, int length, int height, @NotNull Priority priority, @NotNull Plugin plugin) { + this(Slot.fromXY(x, y), length, height, priority, plugin); + } - setX(x); - setY(y); + /** + * Creates a new variable bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this variable bar's items + * @see #VariableBar(Slot, int, int) + * @since 0.10.8 + */ + protected VariableBar(@NotNull Slot slot, int length, int height, @NotNull Plugin plugin) { + this(slot, length, height, Priority.NORMAL, plugin); + } - setPriority(priority); + /** + * Creates a new variable bar + * + * @param x the x coordinate of the bar + * @param y the y coordinate of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param plugin the plugin that will be the owner for this variable bar's items + * @see #VariableBar(int, int) + * @since 0.10.8 + */ + protected VariableBar(int x, int y, int length, int height, @NotNull Plugin plugin) { + this(x, y, length, height, Priority.NORMAL, plugin); + } + + protected VariableBar(int length, int height) { + this(0, 0, length, height); + } + + /** + * Creates a new variable bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @param priority the priority of the bar + * @since 0.10.8 + */ + protected VariableBar(@NotNull Slot slot, int length, int height, @NotNull Priority priority) { + this(slot, length, height, priority, JavaPlugin.getProvidingPlugin(VariableBar.class)); + } + + protected VariableBar(int x, int y, int length, int height, @NotNull Priority priority) { + this(x, y, length, height, priority, JavaPlugin.getProvidingPlugin(VariableBar.class)); + } + + /** + * Creates a new variable bar + * + * @param slot the slot of the bar + * @param length the length of the bar + * @param height the height of the bar + * @since 0.10.8 + */ + protected VariableBar(@NotNull Slot slot, int length, int height) { + this(slot, length, height, Priority.NORMAL); } protected VariableBar(int x, int y, int length, int height) { @@ -184,6 +283,7 @@ public void setHeight(int height) { protected void applyContents(@NotNull VariableBar copy) { copy.x = x; copy.y = y; + copy.slot = slot; copy.length = length; copy.height = height; copy.setPriority(getPriority()); @@ -236,13 +336,20 @@ public void setOrientation(@NotNull Orientation orientation) { @Override public void display(@NotNull InventoryComponent inventoryComponent, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { - int newPaneOffsetX = paneOffsetX + getX(); - int newPaneOffsetY = paneOffsetY + getY(); + Slot slot = getSlot(); + + int newPaneOffsetX = paneOffsetX + slot.getX(maxLength); + int newPaneOffsetY = paneOffsetY + slot.getY(maxLength); int newMaxLength = Math.min(maxLength, getLength()); int newMaxHeight = Math.min(maxHeight, getHeight()); - this.backgroundPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); - this.fillPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); + if (this.backgroundPane.isVisible()) { + this.backgroundPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); + } + + if (this.fillPane.isVisible()) { + this.fillPane.display(inventoryComponent, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight); + } } /** diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Mask.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Mask.java index 55bce832f..901d854a4 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Mask.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Mask.java @@ -154,7 +154,7 @@ public int amountOfEnabledSlots() { * @since 0.5.16 */ public boolean[] getColumn(int index) { - boolean[] column = new boolean[mask[0].length]; + boolean[] column = new boolean[mask.length]; for (int i = 0; i < getHeight(); i++) { column[i] = mask[i][index]; diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Slot.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Slot.java new file mode 100644 index 000000000..7f1037ded --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/pane/util/Slot.java @@ -0,0 +1,254 @@ +package com.github.stefvanschie.inventoryframework.pane.util; + +import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Element; + +import java.util.Objects; + +/** + * A slot represents a position in some type of container. Implementors of this class represent slots in different ways. + * + * @since 0.10.8 + */ +public interface Slot { + + /** + * Gets the x coordinate of this slot. + * + * @param length the length of the parent container + * @return the x coordinate of this slot + * @since 0.10.8 + */ + @Contract(pure = true) + int getX(int length); + + /** + * Gets the y coordinate of this slot. + * + * @param length the length of the parent container + * @return the y coordinate of this slot + * @since 0.10.8 + */ + @Contract(pure = true) + int getY(int length); + + /** + * Deserializes the slot from an element. The slot may either be provided as an (x, y) coordinate pair via the "x" + * and "y" attributes; or as an index via the "index" attribute. If both forms are present, an + * {@link XMLLoadException} will be thrown. If only the "x" or the "y" attribute is present, but not both, an + * {@link XMLLoadException} will be thrown. If none of the aforementioned attributes appear, an + * {@link XMLLoadException} will be thrown. If any of these attributes contain a value that is not an integer, an + * {@link XMLLoadException} will be thrown. Otherwise, this will return a slot based on the present attributes. + * + * @param element the element from which to retrieve the attributes for the slot + * @return the deserialized slot + * @throws XMLLoadException if "x", "y", and "index" attributes are present; if only an "x" attribute is present; if + * only a "y" attribute is present; if no "x", "y", or "index" attribute is present; or if + * the "x", "y", or "index" attribute contain a value that is not an integer. + */ + @NotNull + @Contract(value = "_ -> new", pure = true) + static Slot deserialize(@NotNull Element element) { + boolean hasX = element.hasAttribute("x"); + boolean hasY = element.hasAttribute("y"); + boolean hasIndex = element.hasAttribute("index"); + + if (hasX && hasY && !hasIndex) { + int x, y; + + try { + x = Integer.parseInt(element.getAttribute("x")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("The x attribute does not have an integer as value", exception); + } + + try { + y = Integer.parseInt(element.getAttribute("y")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("The y attribute does not have an integer as value", exception); + } + + return Slot.fromXY(x, y); + } + + if (hasIndex && !hasX && !hasY) { + int index; + + try { + index = Integer.parseInt(element.getAttribute("index")); + } catch (NumberFormatException exception) { + throw new XMLLoadException("The index attribute does not have an integer as value", exception); + } + + return Slot.fromIndex(index); + } + + throw new XMLLoadException("The combination of x, y and index attributes is invalid"); + } + + /** + * Creates a new slot based on an (x, y) coordinate pair. + * + * @param x the x coordinate + * @param y the y coordinate + * @return the slot representing this position + * @since 0.10.8 + */ + @NotNull + @Contract(value = "_, _ -> new", pure = true) + static Slot fromXY(int x, int y) { + return new XY(x, y); + } + + /** + * Creates a new slot based on an index. This index is relative to the parent container this slot will be used in. + * + * @param index the index + * @return the slot representing this relative position + * @since 0.10.8 + */ + @NotNull + @Contract("_ -> new") + static Slot fromIndex(int index) { + return new Indexed(index); + } + + /** + * A class representing a slot based on an (x, y) coordinate pair. + * + * @since 0.10.8 + */ + class XY implements Slot { + + /** + * The (x, y) coordinate pair + */ + private final int x, y; + + /** + * Creates a new slot based on an (x, y) coordinate pair. + * + * @param x the x coordinate + * @param y the y coordinate + * @since 0.10.8 + */ + private XY(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public int getX(int length) { + return this.x; + } + + @Override + public int getY(int length) { + return this.y; + } + + @Override + public boolean equals(@Nullable Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + XY xy = (XY) object; + + return x == xy.x && y == xy.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + } + + /** + * A class representing a slot based on an index. + * + * @since 0.10.8 + */ + class Indexed implements Slot { + + /** + * The index of this slot. + */ + private final int index; + + /** + * Creates a new slot based on an index. + * + * @param index the index of this slot + * @since 0.10.8 + */ + private Indexed(int index) { + this.index = index; + } + + /** + * {@inheritDoc} + * + * If {@code length} is zero, this will throw an {@link IllegalArgumentException}. + * + * @param length {@inheritDoc} + * @return {@inheritDoc} + * @throws IllegalArgumentException when {@code length} is zero + */ + @Override + @Contract(pure = true) + public int getX(int length) { + if (length == 0) { + throw new IllegalArgumentException("Length may not be zero"); + } + + return this.index % length; + } + + /** + * {@inheritDoc} + * + * If {@code length} is zero, this will throw an {@link IllegalArgumentException}. + * + * @param length {@inheritDoc} + * @return {@inheritDoc} + * @throws IllegalArgumentException when {@code length} is zero + */ + @Override + @Contract(pure = true) + public int getY(int length) { + if (length == 0) { + throw new IllegalArgumentException("Length may not be zero"); + } + + return this.index / length; + } + + @Override + public boolean equals(@Nullable Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + Indexed indexed = (Indexed) object; + + return index == indexed.index; + } + + @Override + public int hashCode() { + return index; + } + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/CSVUtil.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/CSVUtil.java index 1f2b75797..89d05da5f 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/CSVUtil.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/CSVUtil.java @@ -1,6 +1,5 @@ package com.github.stefvanschie.inventoryframework.util; -import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; @@ -72,9 +71,7 @@ public static List readAll(@NotNull InputStream inputStream) throws IO array[i] = array[i].substring(1, array[i].length() - 1); } - array[i] = StringUtils.replace(array[i], "\"\"", "\""); - //Restore original code (array[i] = array[i].replace("\"\"", "\"")) - //once we update to Java 11, where it receives the current, faster implementation + array[i] = array[i].replace("\"\"", "\""); //replace unicode characters Matcher matcher = UNICODE_CHARACTER_PATTERN.matcher(array[i]); diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/InventoryViewUtil.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/InventoryViewUtil.java new file mode 100644 index 000000000..8d6327e69 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/InventoryViewUtil.java @@ -0,0 +1,42 @@ +package com.github.stefvanschie.inventoryframework.util; + +import com.github.stefvanschie.inventoryframework.inventoryview.abstraction.AbstractInventoryViewUtil; +import com.github.stefvanschie.inventoryframework.util.version.Version; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A utility class for working with {@link InventoryView}s across different definitions. + * + * @since 0.10.16 + */ +public class InventoryViewUtil { + + /** + * The underlying implementation. + */ + @Nullable + private static AbstractInventoryViewUtil IMPLEMENTATION; + + /** + * Gets the instance of this class to use for the current version. + * + * @return an instance of a utility class + * @since 0.10.16 + */ + @NotNull + @Contract(pure = true) + public static AbstractInventoryViewUtil getInstance() { + if (IMPLEMENTATION == null) { + if (Version.getVersion().isInventoryViewInterface()) { + IMPLEMENTATION = com.github.stefvanschie.inventoryframework.inventoryview.interface_.InventoryViewUtil.getInstance(); + } else { + IMPLEMENTATION = com.github.stefvanschie.inventoryframework.inventoryview.abstractclass.InventoryViewUtil.getInstance(); + } + } + + return IMPLEMENTATION; + } +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/SkullUtil.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/SkullUtil.java index b28b82bc4..befe08bc1 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/SkullUtil.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/SkullUtil.java @@ -1,13 +1,22 @@ package com.github.stefvanschie.inventoryframework.util; +import com.github.stefvanschie.inventoryframework.util.version.Version; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.profile.PlayerProfile; +import org.bukkit.profile.PlayerTextures; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Base64; import java.util.Objects; import java.util.UUID; @@ -51,17 +60,68 @@ public static ItemStack getSkull(@NotNull String id) { * @param id the skull id */ public static void setSkull(@NotNull ItemMeta meta, @NotNull String id) { - GameProfile profile = new GameProfile(UUID.randomUUID(), null); + if (Version.getVersion().isOlderThan(Version.V1_18_2)) { + setSkullReflection(meta, id); + } else { + setSkullProfile(meta, id); + } + } + + /** + * Sets the skull's texture based on the player profile API introduced in 1.18.2. + * + * @param meta the {@link ItemMeta} of the skull to set + * @param id the ID of the skin URL to apply + * @since 0.11.6 + */ + private static void setSkullProfile(@NotNull ItemMeta meta, @NotNull String id) { + if (!(meta instanceof SkullMeta)) { + throw new IllegalArgumentException("Provided item meta is not of a skull"); + } + + PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID()); + PlayerTextures textures = profile.getTextures(); + + try { + textures.setSkin(new URL("http://textures.minecraft.net/texture/" + id)); + } catch (MalformedURLException exception) { + throw new IllegalArgumentException("Provided ID is invalid", exception); + } + + profile.setTextures(textures); + + ((SkullMeta) meta).setOwnerProfile(profile); + } + + /** + * Sets the skull's texture via reflection. + * + * @param meta the {@link ItemMeta} of the skull to set + * @param id the ID of the skin URL to apply + * @since 0.11.6 + */ + private static void setSkullReflection(@NotNull ItemMeta meta, @NotNull String id) { + GameProfile profile = new GameProfile(UUID.randomUUID(), ""); byte[] encodedData = Base64.getEncoder().encode(String.format("{textures:{SKIN:{url:\"%s\"}}}", - "http://textures.minecraft.net/texture/" + id).getBytes()); + "http://textures.minecraft.net/texture/" + id).getBytes()); profile.getProperties().put("textures", new Property("textures", new String(encodedData))); + String itemDisplayName = meta.getDisplayName(); try { Field profileField = meta.getClass().getDeclaredField("profile"); profileField.setAccessible(true); profileField.set(meta, profile); - } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { + + meta.setDisplayName(itemDisplayName); + + // Sets serializedProfile field on meta + // If it does throw NoSuchMethodException this stops, and meta is correct. + // Else it has profile and will set the field. + Method setProfile = meta.getClass().getDeclaredMethod("setProfile", GameProfile.class); + setProfile.setAccessible(true); + setProfile.invoke(meta, profile); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); - } + } catch (NoSuchMethodException ignored) {} } } diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/TriFunction.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/TriFunction.java new file mode 100644 index 000000000..7ce718ca8 --- /dev/null +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/TriFunction.java @@ -0,0 +1,25 @@ +package com.github.stefvanschie.inventoryframework.util; + +/** + * A function that takes three arguments and returns a result. + * + * @param the type of the first argument + * @param the type of the second argument + * @param the type of the third argument + * @param the type of the result + * @since 0.10.8 + */ +@FunctionalInterface +public interface TriFunction { + + /** + * Applies this function to the given arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @return the result value + * @since 0.10.8 + */ + R apply(A a, B b, C c); +} diff --git a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/XMLUtil.java b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/XMLUtil.java index ede880dbc..1f3586bc9 100644 --- a/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/XMLUtil.java +++ b/IF/src/main/java/com/github/stefvanschie/inventoryframework/util/XMLUtil.java @@ -62,6 +62,30 @@ public static Consumer loadOnEventAttribute(@NotNull Object return null; } + /** + * Invokes the method by the given name on the given instance with the provided argument. The method should have + * the exact name specified and the exact parameter as specified. If the method cannot be accessed or found, this + * will throw an {@link XMLLoadException}. + * + * @param instance the instance on which to call the method + * @param methodName the name of the method to invoke + * @param argument the argument to provide for the invocation + * @param parameter the parameter of the method + * @since 0.10.3 + * @throws XMLLoadException if the method cannot be accessed or found + */ + public static void invokeMethod(@NotNull Object instance, @NotNull String methodName, @NotNull Object argument, + @NotNull Class parameter) { + try { + Method method = instance.getClass().getMethod(methodName, parameter); + + method.setAccessible(true); + method.invoke(instance, argument); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { + throw new XMLLoadException(exception); + } + } + /** * Sets a field from the given instance and element to the specified value * 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 aa2a2edca..1b4045117 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 @@ -2,9 +2,13 @@ import com.github.stefvanschie.inventoryframework.exception.UnsupportedVersionException; import org.bukkit.Bukkit; +import org.bukkit.inventory.InventoryView; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.EnumSet; + /** * The different supported NMS versions * @@ -13,39 +17,198 @@ public enum Version { /** - * Version 1.14 R1 + * Version 1.16.5 * - * @since 0.8.0 + * @since 0.12.0 */ - V1_14_R1, + V1_16_5, /** - * Version 1.15 R1 + * Version 1.17.1 * - * @since 0.8.0 + * @since 0.10.0 */ - V1_15_R1, + V1_17_1, /** - * Version 1.16 R1 + * Version 1.18.2 * - * @since 0.8.0 + * @since 0.10.5 */ - V1_16_R1, + V1_18_2, /** - * Version 1.16 R2 + * Version 1.19.4 * - * @since 0.8.0 + * @since 0.10.9 */ - V1_16_R2, + V1_19_4, /** - * Version 1.16 R3 + * Version 1.20.0 * - * @since 0.8.0 + * @since 0.10.14 + */ + V1_20_0, + + /** + * Version 1.20.1 + * + * @since 0.10.14 + */ + V1_20_1, + + /** + * Version 1.20.2 + * + * @since 0.10.12 + */ + V1_20_2, + + /** + * Version 1.20.3 - 1.20.4 + * + * @since 0.10.13 + */ + V1_20_3_4, + + /** + * Version 1.20.5 + * + * @since 0.10.14 + */ + V1_20_5, + + /** + * Version 1.20.6 + * + * @since 0.10.14 + */ + V1_20_6, + + /** + * Version 1.21.0 + * + * @since 0.10.18 + */ + V1_21_0, + + /** + * Version 1.21.1 + * + * @since 0.10.18 + */ + V1_21_1, + + /** + * Version 1.21.2 - 1.21.3 + * + * @since 0.10.18 + */ + V1_21_2_3, + + /** + * Version 1.21.4 + * + * @since 0.10.19 + */ + V1_21_4, + + /** + * Version 1.21.5 + * + * @since 0.11.0 */ - V1_16_R3; + V1_21_5, + + /** + * Version 1.21.6 - 1.21.8 + * + * @since 0.11.3 + */ + V1_21_6_8, + + /** + * Version 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ + V1_21_9_10, + + /** + * Version 1.21.11 + * + * @since 0.11.6 + */ + V1_21_11; + + /** + * 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_6, + V1_21_0, V1_21_1, V1_21_2_3, V1_21_4, V1_21_5, V1_21_6_8, V1_21_9_10, V1_21_11 + ); + + /** + * A collection of versions on which legacy smithing tables ae available. + */ + @NotNull + private static final Collection<@NotNull Version> LEGACY_SMITHING_TABLE_VERSIONS = EnumSet.of( + V1_16_5, V1_17_1, V1_18_2, V1_19_4 + ); + + /** + * A collection of versions on which {@link InventoryView} is an interface. + */ + @NotNull + private static final Collection<@NotNull Version> INTERFACE_INVENTORY_VIEW = EnumSet.of( + V1_21_0, V1_21_1, V1_21_2_3, V1_21_4, V1_21_5, V1_21_6_8, V1_21_9_10, V1_21_11 + ); + + /** + * Checks whether the {@link InventoryView} class is an interface on this version. + * + * @return true if the class is an interface, false otherwise + * @since 0.10.16 + */ + @Contract(pure = true) + public boolean isInventoryViewInterface() { + return INTERFACE_INVENTORY_VIEW.contains(this); + } + + /** + * Checks if this version is older than the provided version. + * + * @param version the version to check if it is newer + * @return true if this version is older, false otherwise + * @since 0.11.6 + */ + public boolean isOlderThan(@NotNull Version version) { + return ordinal() < version.ordinal(); + } + + /** + * Checks whether modern smithing tables exist on this version. Returns true if they do, otherwise false. + * + * @return true if modern smithing tables are available + * @since 0.10.10 + */ + boolean existsModernSmithingTable() { + return MODERN_SMITHING_TABLE_VERSIONS.contains(this); + } + + /** + * Checks whether legacy smithing tables exist on this version. Returns true if they do, otherwise false. + * + * @return true if legacy smithing tables are available + * @since 0.10.10 + */ + @Contract(pure = true) + boolean existsLegacySmithingTable() { + return LEGACY_SMITHING_TABLE_VERSIONS.contains(this); + } /** * Gets the version currently being used. If the used version is not supported, an @@ -57,19 +220,50 @@ public enum Version { @NotNull @Contract(pure = true) public static Version getVersion() { - String version = Bukkit.getServer().getClass().getPackage().getName(); - - switch (version.substring(version.lastIndexOf('.') + 1)) { - case "v1_14_R1": - return V1_14_R1; - case "v1_15_R1": - return V1_15_R1; - case "v1_16_R1": - return V1_16_R1; - case "v1_16_R2": - return V1_16_R2; - case "v1_16_R3": - return V1_16_R3; + String version = Bukkit.getBukkitVersion().split("-")[0]; + + switch (version) { + case "1.16.5": + return V1_16_5; + case "1.17.1": + return V1_17_1; + case "1.18.2": + return V1_18_2; + case "1.19.4": + return V1_19_4; + case "1.20": + return V1_20_0; + case "1.20.1": + return V1_20_1; + case "1.20.2": + return V1_20_2; + case "1.20.3": + case "1.20.4": + return V1_20_3_4; + case "1.20.5": + return V1_20_5; + case "1.20.6": + return V1_20_6; + case "1.21": + return V1_21_0; + case "1.21.1": + return V1_21_1; + case "1.21.2": + case "1.21.3": + return V1_21_2_3; + case "1.21.4": + return V1_21_4; + case "1.21.5": + return V1_21_5; + case "1.21.6": + case "1.21.7": + case "1.21.8": + return V1_21_6_8; + case "1.21.9": + case "1.21.10": + return V1_21_9_10; + case "1.21.11": + return V1_21_11; 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 c4f108e10..8a99e4e51 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 @@ -2,7 +2,7 @@ import com.github.stefvanschie.inventoryframework.abstraction.*; import com.github.stefvanschie.inventoryframework.exception.UnsupportedVersionException; -import org.bukkit.inventory.InventoryHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.LegacySmithingTableInventoryImpl; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -41,11 +41,21 @@ public class VersionMatcher { */ private static final EnumMap> GRINDSTONE_INVENTORIES; + /** + * The different merchant inventories for different versions + */ + private static final EnumMap> MERCHANT_INVENTORIES; + /** * The different smithing table inventories for different versions */ private static final EnumMap> SMITHING_TABLE_INVENTORIES; + /** + * The different legacy smithing table inventories for different versions + */ + private static final EnumMap> LEGACY_SMITHING_TABLE_INVENTORIES; + /** * The different stonecutter inventories for different versions */ @@ -55,15 +65,14 @@ public class VersionMatcher { * Gets a new anvil inventory for the specified version of the specified inventory holder. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the anvil inventory - * @since 0.8.0 + * @since 0.11.0 */ @NotNull @Contract(pure = true) - public static AnvilInventory newAnvilInventory(@NotNull Version version, @NotNull InventoryHolder inventoryHolder) { + public static AnvilInventory newAnvilInventory(@NotNull Version version) { try { - return ANVIL_INVENTORIES.get(version).getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + return ANVIL_INVENTORIES.get(version).getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -74,16 +83,14 @@ public static AnvilInventory newAnvilInventory(@NotNull Version version, @NotNul * Gets a new beacon inventory for the specified version of the specified inventory holder. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the beacon inventory - * @since 0.8.0 + * @since 0.11.0 */ @NotNull @Contract(pure = true) - public static BeaconInventory newBeaconInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { + public static BeaconInventory newBeaconInventory(@NotNull Version version) { try { - return BEACON_INVENTORIES.get(version).getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + return BEACON_INVENTORIES.get(version).getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -94,18 +101,14 @@ public static BeaconInventory newBeaconInventory(@NotNull Version version, * Gets a new cartography table inventory for the specified version of the specified inventory holder. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the cartography table inventory - * @since 0.8.0 + * @since 0.11.0 */ @NotNull @Contract(pure = true) - public static CartographyTableInventory newCartographyTableInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { + public static CartographyTableInventory newCartographyTableInventory(@NotNull Version version) { try { - Class clazz = CARTOGRAPHY_TABLE_INVENTORIES.get(version); - - return clazz.getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + return CARTOGRAPHY_TABLE_INVENTORIES.get(version).getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -116,18 +119,16 @@ public static CartographyTableInventory newCartographyTableInventory(@NotNull Ve * Gets a new enchanting table inventory for the specified version of the specified inventory holder. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the enchanting table inventory * @since 0.8.0 */ @NotNull @Contract(pure = true) - public static EnchantingTableInventory newEnchantingTableInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { + public static EnchantingTableInventory newEnchantingTableInventory(@NotNull Version version) { try { Class clazz = ENCHANTING_TABLE_INVENTORIES.get(version); - return clazz.getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + return clazz.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -138,47 +139,63 @@ public static EnchantingTableInventory newEnchantingTableInventory(@NotNull Vers * Gets a new grindstone inventory for the specified version of the specified inventory holder. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the grindstone inventory * @since 0.8.0 */ @NotNull @Contract(pure = true) - public static GrindstoneInventory newGrindstoneInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { + public static GrindstoneInventory newGrindstoneInventory(@NotNull Version version) { try { Class clazz = GRINDSTONE_INVENTORIES.get(version); - return clazz.getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + return clazz.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); } } + /** + * Gets a new merchant inventory for the specified version. + * + * @param version the version to get the inventory of + * @return the merchant inventory + * @since 0.10.1 + */ + @NotNull + @Contract(pure = true) + public static MerchantInventory newMerchantInventory(@NotNull Version version) { + try { + return MERCHANT_INVENTORIES.get(version).getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException exception) { + throw new IllegalStateException(exception); + } + } + /** * Gets a new smithing table inventory for the specified version of the specified inventory holder. If a smithing * table is requested for a version that does not have smithing tables, an {@link UnsupportedVersionException} is * thrown. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder * @return the smithing table inventory - * @since 0.8.0 + * @since 0.10.9 * @throws UnsupportedVersionException when a smithing table is requested on a version without smithing tables */ @NotNull @Contract(pure = true) - public static SmithingTableInventory newSmithingTableInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { - if (version == Version.V1_14_R1 || version == Version.V1_15_R1) { + public static SmithingTableInventory newModernSmithingTableInventory(@NotNull Version version) { + if (!version.existsModernSmithingTable() && !version.existsLegacySmithingTable()) { throw new UnsupportedVersionException("Smithing tables didn't exist in version " + version); } - try { - Class clazz = SMITHING_TABLE_INVENTORIES.get(version); + if (!version.existsModernSmithingTable()) { + throw new UnsupportedVersionException("Modern smithing tables didn't exist in version " + version); + } - return clazz.getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + try { + return SMITHING_TABLE_INVENTORIES.get(version).getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -186,21 +203,46 @@ public static SmithingTableInventory newSmithingTableInventory(@NotNull Version } /** - * Gets a new stonecutter inventory for the specified version of the specified inventory holder. + * Gets a new legacy smithing table inventory for the specified version of the specified inventory holder. If a + * smithing table is requested for a version that does not have smithing tables, an + * {@link UnsupportedVersionException} is thrown. * * @param version the version to get the inventory of - * @param inventoryHolder the inventory holder - * @return the stonecutter inventory + * @return the smithing table inventory * @since 0.8.0 + * @throws UnsupportedVersionException when a smithing table is requested on a version without smithing tables */ @NotNull @Contract(pure = true) - public static StonecutterInventory newStonecutterInventory(@NotNull Version version, - @NotNull InventoryHolder inventoryHolder) { + public static SmithingTableInventory newSmithingTableInventory(@NotNull Version version) { + if (!version.existsModernSmithingTable() && !version.existsLegacySmithingTable()) { + throw new UnsupportedVersionException("Smithing tables didn't exist in version " + version); + } + + if (!version.existsLegacySmithingTable()) { + throw new UnsupportedVersionException("Legacy smithing tables don't exist in version " + version); + } + try { - Class clazz = STONECUTTER_INVENTORIES.get(version); + return LEGACY_SMITHING_TABLE_INVENTORIES.get(version).getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException exception) { + throw new IllegalStateException(exception); + } + } - return clazz.getConstructor(InventoryHolder.class).newInstance(inventoryHolder); + /** + * Gets a new stonecutter inventory for the specified version. + * + * @param version the version to get the inventory of + * @return the stonecutter inventory + * @since 0.11.0 + */ + @NotNull + @Contract(pure = true) + public static StonecutterInventory newStonecutterInventory(@NotNull Version version) { + try { + return STONECUTTER_INVENTORIES.get(version).getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { throw new IllegalStateException(exception); @@ -209,83 +251,311 @@ public static StonecutterInventory newStonecutterInventory(@NotNull Version vers static { ANVIL_INVENTORIES = new EnumMap<>(Version.class); - ANVIL_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.AnvilInventoryImpl.class); - ANVIL_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.AnvilInventoryImpl.class); - ANVIL_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.AnvilInventoryImpl.class); - ANVIL_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.AnvilInventoryImpl.class); - ANVIL_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_20_3_4, + 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); + ANVIL_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.AnvilInventoryImpl.class); + ANVIL_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.AnvilInventoryImpl.class); BEACON_INVENTORIES = new EnumMap<>(Version.class); - BEACON_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.BeaconInventoryImpl.class); - BEACON_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.BeaconInventoryImpl.class); - BEACON_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.BeaconInventoryImpl.class); - BEACON_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.BeaconInventoryImpl.class); - BEACON_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_20_3_4, + 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); + BEACON_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.BeaconInventoryImpl.class); + BEACON_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.BeaconInventoryImpl.class); CARTOGRAPHY_TABLE_INVENTORIES = new EnumMap<>(Version.class); - CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.CartographyTableInventoryImpl.class); - CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.CartographyTableInventoryImpl.class); - CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.CartographyTableInventoryImpl.class); - CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.CartographyTableInventoryImpl.class); - CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_20_3_4, + 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); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.CartographyTableInventoryImpl.class); + CARTOGRAPHY_TABLE_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.CartographyTableInventoryImpl.class); ENCHANTING_TABLE_INVENTORIES = new EnumMap<>(Version.class); - ENCHANTING_TABLE_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.EnchantingTableInventoryImpl.class); - ENCHANTING_TABLE_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.EnchantingTableInventoryImpl.class); - ENCHANTING_TABLE_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.EnchantingTableInventoryImpl.class); - ENCHANTING_TABLE_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.EnchantingTableInventoryImpl.class); - ENCHANTING_TABLE_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_20_3_4, + 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); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.EnchantingTableInventoryImpl.class); + ENCHANTING_TABLE_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.EnchantingTableInventoryImpl.class); GRINDSTONE_INVENTORIES = new EnumMap<>(Version.class); - GRINDSTONE_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.GrindstoneInventoryImpl.class); - GRINDSTONE_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.GrindstoneInventoryImpl.class); - GRINDSTONE_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.GrindstoneInventoryImpl.class); - GRINDSTONE_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.GrindstoneInventoryImpl.class); - GRINDSTONE_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_20_3_4, + 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); + GRINDSTONE_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.GrindstoneInventoryImpl.class); + GRINDSTONE_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.GrindstoneInventoryImpl.class); + + MERCHANT_INVENTORIES = new EnumMap<>(Version.class); + MERCHANT_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_20_3_4, + 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); + MERCHANT_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.MerchantInventoryImpl.class); + MERCHANT_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.MerchantInventoryImpl.class); SMITHING_TABLE_INVENTORIES = new EnumMap<>(Version.class); - SMITHING_TABLE_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.SmithingTableInventoryImpl.class); - SMITHING_TABLE_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.SmithingTableInventoryImpl.class); - SMITHING_TABLE_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_20_3_4, + 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); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.SmithingTableInventoryImpl.class); + SMITHING_TABLE_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.SmithingTableInventoryImpl.class); + + LEGACY_SMITHING_TABLE_INVENTORIES = new EnumMap<>(Version.class); + LEGACY_SMITHING_TABLE_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.SmithingTableInventoryImpl.class); + LEGACY_SMITHING_TABLE_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.SmithingTableInventoryImpl.class); + LEGACY_SMITHING_TABLE_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.SmithingTableInventoryImpl.class); + LEGACY_SMITHING_TABLE_INVENTORIES.put(Version.V1_19_4, + LegacySmithingTableInventoryImpl.class); STONECUTTER_INVENTORIES = new EnumMap<>(Version.class); - STONECUTTER_INVENTORIES.put(Version.V1_14_R1, - com.github.stefvanschie.inventoryframework.nms.v1_14_R1.StonecutterInventoryImpl.class); - STONECUTTER_INVENTORIES.put(Version.V1_15_R1, - com.github.stefvanschie.inventoryframework.nms.v1_15_R1.StonecutterInventoryImpl.class); - STONECUTTER_INVENTORIES.put(Version.V1_16_R1, - com.github.stefvanschie.inventoryframework.nms.v1_16_R1.StonecutterInventoryImpl.class); - STONECUTTER_INVENTORIES.put(Version.V1_16_R2, - com.github.stefvanschie.inventoryframework.nms.v1_16_R2.StonecutterInventoryImpl.class); - STONECUTTER_INVENTORIES.put(Version.V1_16_R3, - com.github.stefvanschie.inventoryframework.nms.v1_16_R3.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_16_5, + com.github.stefvanschie.inventoryframework.nms.v1_16_5.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_17_1, + com.github.stefvanschie.inventoryframework.nms.v1_17_1.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_18_2, + com.github.stefvanschie.inventoryframework.nms.v1_18_2.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_19_4, + com.github.stefvanschie.inventoryframework.nms.v1_19_4.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_20_0, + com.github.stefvanschie.inventoryframework.nms.v1_20_0.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_20_1, + com.github.stefvanschie.inventoryframework.nms.v1_20_1.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_20_2, + com.github.stefvanschie.inventoryframework.nms.v1_20_2.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_20_3_4, + 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); + STONECUTTER_INVENTORIES.put(Version.V1_21_0, + com.github.stefvanschie.inventoryframework.nms.v1_21_0.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_1, + com.github.stefvanschie.inventoryframework.nms.v1_21_1.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_2_3, + com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_4, + com.github.stefvanschie.inventoryframework.nms.v1_21_4.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_5, + com.github.stefvanschie.inventoryframework.nms.v1_21_5.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_6_8, + com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_9_10, + com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.StonecutterInventoryImpl.class); + STONECUTTER_INVENTORIES.put(Version.V1_21_11, + com.github.stefvanschie.inventoryframework.nms.v1_21_11.StonecutterInventoryImpl.class); } } diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/OutlinePaneTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/OutlinePaneTest.java index 3522c1185..29f0a4411 100644 --- a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/OutlinePaneTest.java +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/OutlinePaneTest.java @@ -24,6 +24,7 @@ void testCopy() { original.flipHorizontally(true); original.flipVertically(true); original.applyMask(new Mask("0")); + original.align(OutlinePane.Alignment.CENTER); OutlinePane copy = original.copy(); @@ -42,6 +43,7 @@ void testCopy() { assertEquals(original.isFlippedHorizontally(), copy.isFlippedHorizontally()); assertEquals(original.isFlippedVertically(), copy.isFlippedVertically()); assertEquals(original.getMask(), copy.getMask()); + assertEquals(original.getAlignment(), copy.getAlignment()); assertEquals(original.getUUID(), copy.getUUID()); } } diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPaneTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPaneTest.java index 6be43125d..6df185eea 100644 --- a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPaneTest.java +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/PaginatedPaneTest.java @@ -1,15 +1,140 @@ package com.github.stefvanschie.inventoryframework.pane; -import com.github.stefvanschie.inventoryframework.font.util.Font; -import com.github.stefvanschie.inventoryframework.pane.component.Label; -import com.github.stefvanschie.inventoryframework.pane.component.PercentageBar; -import com.github.stefvanschie.inventoryframework.pane.component.ToggleButton; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; public class PaginatedPaneTest { + @Test + void testAddPageEmpty() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane = new StaticPane(0, 0, 1, 1); + + assertDoesNotThrow(() -> { + paginatedPane.addPage(staticPane); + + Collection panes = paginatedPane.getPanes(0); + + assertEquals(1, panes.size()); + assertSame(staticPane, panes.iterator().next()); + }); + } + + @Test + void testAddPageNotEmpty() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane1 = new StaticPane(0, 0, 1, 1); + StaticPane staticPane2 = new StaticPane(0, 0, 1, 1); + + paginatedPane.addPane(0, staticPane1); + + assertDoesNotThrow(() -> { + paginatedPane.addPage(staticPane2); + + Collection panes = paginatedPane.getPanes(1); + + assertEquals(1, panes.size()); + assertSame(staticPane2, panes.iterator().next()); + }); + } + + @Test + void testAddPaneNegative() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane = new StaticPane(0, 0, 1, 1); + + assertThrows(IllegalArgumentException.class, () -> paginatedPane.addPane(-1, staticPane)); + } + + @Test + void testAddPaneExisting() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane1 = new StaticPane(0, 0, 1, 1); + StaticPane staticPane2 = new StaticPane(0, 0, 1, 1); + + Set elements = new HashSet<>(); + + elements.add(staticPane1); + elements.add(staticPane2); + + paginatedPane.addPane(0, staticPane1); + + assertDoesNotThrow(() -> { + paginatedPane.addPane(0, staticPane2); + + Collection panes = paginatedPane.getPanes(0); + + assertEquals(elements.size(), panes.size()); + assertTrue(elements.containsAll(panes)); + }); + } + + @Test + void testAddPaneAfter() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane1 = new StaticPane(0, 0, 1, 1); + StaticPane staticPane2 = new StaticPane(0, 0, 1, 1); + + paginatedPane.addPane(0, staticPane1); + + assertDoesNotThrow(() -> { + paginatedPane.addPane(1, staticPane2); + + Collection panes0 = paginatedPane.getPanes(0); + + assertEquals(1, panes0.size()); + assertEquals(staticPane1, panes0.iterator().next()); + + Collection panes1 = paginatedPane.getPanes(1); + + assertEquals(1, panes1.size()); + assertEquals(staticPane2, panes1.iterator().next()); + }); + } + + @Test + void testAddPaneBeyond() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane1 = new StaticPane(0, 0, 1, 1); + StaticPane staticPane2 = new StaticPane(0, 0, 1, 1); + + paginatedPane.addPane(0, staticPane1); + + assertThrows(IllegalArgumentException.class, () -> paginatedPane.addPane(2, staticPane2)); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 0}) + void testSetPageOutside(int index) { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + assertThrows(ArrayIndexOutOfBoundsException.class, () -> paginatedPane.setPage(index)); + } + + @Test + void testSetPage() { + PaginatedPane paginatedPane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane1 = new StaticPane(0, 0, 1, 1); + + paginatedPane.addPage(staticPane1); + + assertDoesNotThrow(() -> paginatedPane.setPage(0)); + } + @Test void testCopy() { PaginatedPane original = new PaginatedPane(5, 5, 4, 1, Pane.Priority.NORMAL); @@ -37,4 +162,28 @@ void testCopy() { assertEquals(original.getPages(), copy.getPages()); assertEquals(original.getUUID(), copy.getUUID()); } + + @Test + void testDeletePageExists() { + PaginatedPane pane = new PaginatedPane(0, 0, 1, 1); + + StaticPane staticPane = new StaticPane(0, 0, 1, 1); + + pane.addPane(0, new StaticPane(0, 0, 1, 1)); + pane.addPane(1, staticPane); + + pane.deletePage(0); + + assertEquals(1, pane.getPages()); + assertEquals(1, pane.getPanes(0).size()); + assertSame(staticPane, pane.getPanes(0).toArray(new Pane[0])[0]); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 0}) + void testDeletePageNotExists(int index) { + PaginatedPane pane = new PaginatedPane(0, 0, 1, 1); + + assertDoesNotThrow(() -> pane.deletePage(index)); + } } diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/MaskTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/MaskTest.java index 3545e05ab..189af67ef 100644 --- a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/MaskTest.java +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/MaskTest.java @@ -39,6 +39,11 @@ void testGetColumn() { "10", "00" ).getColumn(0)); + + assertArrayEquals(new boolean[] {true, false}, new Mask( + "1", + "0" + ).getColumn(0)); } @Test diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/SlotTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/SlotTest.java new file mode 100644 index 000000000..2ad037ef8 --- /dev/null +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/pane/util/SlotTest.java @@ -0,0 +1,23 @@ +package com.github.stefvanschie.inventoryframework.pane.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class SlotTest { + + @Test + void testEquals() { + assertEquals(Slot.fromXY(0, 0), Slot.fromXY(0, 0)); + assertNotEquals(Slot.fromXY(0, 1), Slot.fromXY(1, 0)); + + assertEquals(Slot.fromIndex(0), Slot.fromIndex(0)); + assertNotEquals(Slot.fromIndex(0), Slot.fromIndex(1)); + } + + @Test + void testHashCode() { + assertEquals(Slot.fromXY(0, 0).hashCode(), Slot.fromXY(0, 0).hashCode()); + assertEquals(Slot.fromIndex(0).hashCode(), Slot.fromIndex(0).hashCode()); + } +} diff --git a/IF/src/test/java/com/github/stefvanschie/inventoryframework/util/PaginatedPaneTest.java b/IF/src/test/java/com/github/stefvanschie/inventoryframework/util/PaginatedPaneTest.java index 1f22d9929..1808ba909 100644 --- a/IF/src/test/java/com/github/stefvanschie/inventoryframework/util/PaginatedPaneTest.java +++ b/IF/src/test/java/com/github/stefvanschie/inventoryframework/util/PaginatedPaneTest.java @@ -15,7 +15,6 @@ public class PaginatedPaneTest { void testGetPanesNonExistentPage() { PaginatedPane pane = new PaginatedPane(1, 1); - //noinspection ResultOfMethodCallIgnored assertThrows(IllegalArgumentException.class, () -> pane.getPanes(0)); } diff --git a/README.md b/README.md index a6d6353e1..592956aca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # IF Discord guild -*This framework works for Minecraft versions 1.14-1.16* +*This framework works for Minecraft versions 1.16.5, 1.17.1, 1.18.2, 1.19.4, and 1.20-1.21* An inventory framework for managing GUIs @@ -14,7 +14,7 @@ To add this project as a dependency to your pom.xml, add the following to your p com.github.stefvanschie.inventoryframework IF - 0.9.8 + 0.11.6 ``` The project is in the Central Repository, so specifying a repository is not needed. @@ -24,7 +24,7 @@ Now in order to shade the project into your project, add the following to your p org.apache.maven.plugins maven-shade-plugin - 3.2.2 + 3.5.2 ${project.build.directory}/dependency-reduced-pom.xml @@ -50,7 +50,7 @@ Replace [YOUR PACKAGE] with the top-level package of your project. To add this project as a dependency for your Gradle project, make sure your `dependencies` section of your build.gradle looks like the following: ```Groovy dependencies { - compile 'com.github.stefvanschie.inventoryframework:IF:0.9.8' + implementation 'com.github.stefvanschie.inventoryframework:IF:0.11.6' // ... } ``` @@ -63,7 +63,10 @@ repositories { ``` In order to include the project in your own project, you will need to use the `shadowJar` plugin. If you don't have it already, add the following to the top of your file: ```Groovy -apply plugin: 'com.github.johnrengelman.shadow' +plugins { + // ... + id "com.github.johnrengelman.shadow" version "7.1.2" +} ``` To relocate the project's classes to your own namespace, add the following, with [YOUR PACKAGE] being the top-level package of your project: ```Groovy @@ -72,11 +75,135 @@ shadowJar { } ``` +## Dependency via plugin.yml +IF does **not** support declaring the dependency via the libraries section in the plugin.yml. Please make use of a build tool as described above to use IF as a dependency. + ## Building from source -If you want to build this project from source, run the following from Git Bash: +If you want to build this project from source, run the following: git clone https://github.com/stefvanschie/IF.git - cd IF - mvn clean package -The build can then be found in /IF/target/. +This will clone this repository to your device. This project relies on NMS, for which the dependencies are not available online. Because of this, you'll need to follow additional steps to obtain all these dependencies locally. + +### Installing Paper manually +For versions 1.15-1.16, we have to manually install Paper. Run the following scripts for each version to install the dependencies locally. Running these commands generate additional files in the folder where you execute them. To ensure that you don't accidentallly overwrite other files, execute this in an empty folder. The files that get created can be deleted afterwards (either after installing a single version or after installing all of them), since they're no longer necessary. + +#### 1.16.5 +``` +wget https://api.papermc.io/v2/projects/paper/versions/1.16.5/builds/794/downloads/paper-1.16.5-794.jar -O paperclip/paper-1.16.5.jar +java -jar paper-1.16.5.jar +mvn install:install-file -Dfile=cache/patched_1.16.5.jar -DgroupId="io.papermc" -DartifactId="paper" -Dversion="1.16.5-R0.1-SNAPSHOT" -Dpackaging="jar" -DgeneratePom="true" +``` + +### Installing Paper via the maven plugin +For versions 1.17-1.20.4, we use Paper via the [paper-nms-maven-plugin](https://github.com/Alvinn8/paper-nms-maven-plugin). To install these versions locally, we must run a few maven commands. These commands should be ran in the root directory of the project. +``` +mvn paper-nms:init -pl nms/1_17_1 +mvn paper-nms:init -pl nms/1_18_2 +mvn paper-nms:init -pl nms/1_19_4 +mvn paper-nms:init -pl nms/1_20_0-1 +mvn paper-nms:init -pl nms/1_20_2 +mvn paper-nms:init -pl nms/1_20_3-4 +``` + +### Installing Spigot via BuildTools +For versions 1.20.5-1.21.8, we use BuildTools. To install these versions, we run the following commands. +``` +wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O BuildTools.jar + +git clone https://hub.spigotmc.org/stash/scm/spigot/bukkit.git Bukkit +cd Bukkit +git checkout 304e83eb384c338546aa96eea51388e0e8407e26 +cd .. + +git clone https://hub.spigotmc.org/stash/scm/spigot/craftbukkit.git CraftBukkit +cd CraftBukkit +git checkout 91b1fc3f1cf89e2591367dca1fa7362fe376f289 +cd .. + +git clone https://hub.spigotmc.org/stash/scm/spigot/spigot.git Spigot +cd Spigot +git checkout b698b49caf14f97a717afd67e13fd7ac59f51089 +cd .. + +git clone https://hub.spigotmc.org/stash/scm/spigot/builddata.git BuildData +cd BuildData +git checkout a7f7c2118b877fde4cf0f32f1f730ffcdee8e9ee +cd .. + +java -jar BuildTools.jar --remapped --disable-java-check --dont-update +java -jar BuildTools.jar --rev 1.20.6 --remapped --disable-java-check + +cd Bukkit +git checkout 2ec53f498e32b3af989cb24672fc54dfab087154 +cd .. + +cd CraftBukkit +git checkout 8ee6fd1b8db9896590aa321d0199453de1fc35db +cd .. + +cd Spigot +git checkout fb8fb722a327a2f9f097f2ded700ac5de8157408 +cd .. + +cd BuildData +git checkout ae1e7b1e31cd3a3892bb05a6ccdcecc48c73c455 +cd .. + +java -jar BuildTools.jar --remapped --disable-java-check --dont-update +java -jar BuildTools.jar --rev 1.21.1 --remapped --disable-java-check +java -jar BuildTools.jar --rev 1.21.3 --remapped --disable-java-check +java -jar BuildTools.jar --rev 1.21.4 --remapped --disable-java-check +java -jar BuildTools.jar --rev 1.21.5 --remapped --disable-java-check +java -jar BuildTools.jar --rev 1.21.8 --remapped --disable-java-check +java -jar BuildTools.jar --rev 1.21.10 --remapped --disable-java-check +``` + +Your environment is now set up correctly. To create a build, run the following inside the root folder of the project. +``` +mvn clean package +``` +Your build is now available in the /IF/target folder. + +## Adventure support + +IF supports [Adventure](https://github.com/KyoriPowered/adventure), but does not shade it in itself. +The use of Adventure `Component`s instead of legacy `String`s is completely optional. +If you do not wish to use Adventure you can safely ignore all `TextHolder` related methods. + +### What is Adventure? + +Adventure is a library that adds proper modern text support to Minecraft. +Modern text is represented using bungee-chat and `BaseComponent` instances in Spigot. +Adventure is an alternative to bungee-chat and offers more features. + +### Using Adventure on 1.16.5+ Paper + +You don't need to import/shade anything for Adventure support in this case! + +*Note: Paper only supports Adventure on build 473 and above. If you aren't running months old builds, then you are fine.* + +### Using Adventure on Spigot and older Paper + +On Spigot Adventure isn't included in the server, therefore you have to shade and relocate it yourself. +The following dependencies need to be imported and shaded: +- adventure-api +- adventure-platform-bukkit + +Please consult the [Adventure documentation](https://docs.adventure.kyori.net/) for more information. + +### How to use Adventure `Component`s + +Example of migration from legacy `String` to Adventure `Component`: + - legacy: `namedGui.setTitle("My Title!");` + - Adventure: `namedGui.setTitle(ComponentHolder.of(Component.text("My Title!")));` + +We apologize for the boilerplate (the `ComponentHolder.of(...)` call), but that was the only way to not make IF hard-depend on Adventure. + +Full Adventure support is only achieved when your server natively supports Adventure (it is running Paper) and your plugin depends on Paper (instead of Spigot). +In other words, you won't benefit from Adventure as much if you use Spigot instead of Paper. +This is because when Adventure is relocated we have to convert everything back to legacy `String`s before passing them to the Bukkit API. + +--- + +NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT. diff --git a/adventure-support/pom.xml b/adventure-support/pom.xml new file mode 100644 index 000000000..4c7eac3b7 --- /dev/null +++ b/adventure-support/pom.xml @@ -0,0 +1,39 @@ + + + + IF-parent + com.github.stefvanschie.inventoryframework + 0.12.0-SNAPSHOT + + 4.0.0 + + adventure-support + + + true + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + com.destroystokyo.paper + paper-api + 1.16.5-R0.1-SNAPSHOT + provided + + + net.kyori + adventure-api + ${adventure.version} + provided + + + diff --git a/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ComponentHolder.java b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ComponentHolder.java new file mode 100644 index 000000000..3d6a4b587 --- /dev/null +++ b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ComponentHolder.java @@ -0,0 +1,176 @@ +package com.github.stefvanschie.inventoryframework.adventuresupport; + +import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * Wrapper of an Adventure {@link Component}. + * + * @since 0.10.0 + */ +public abstract class ComponentHolder extends TextHolder { + + /** + * Whether the server platform natively supports Adventure. + * A null value indicates that we don't yet know this: it hasn't been determined yet. + * This field should not be used directly, use {@link #isNativeAdventureSupport()} instead. + */ + @Nullable + private static Boolean nativeAdventureSupport; + + /** + * The serializer to use when converting wrapped values to legacy strings. + * A null value indicates that we haven't created the serializer yet. + * This field should not be used directly, use {@link #getLegacySerializer()} instead. + */ + @Nullable + private static LegacyComponentSerializer legacySerializer; + + /** + * Wraps the specified Adventure component. + * + * @param value the value to wrap + * @return an instance that wraps the specified value + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static ComponentHolder of(@NotNull Component value) { + Validate.notNull(value, "value mustn't be null"); + return isNativeAdventureSupport() + ? new NativeComponentHolder(value) + : new ForeignComponentHolder(value); + } + + /** + * Gets whether the server platform natively supports Adventure. + * Native Adventure support means that eg. {@link ItemMeta#displayName(Component)} + * is a valid method. + * + * @return whether the server platform natively supports Adventure + * @since 0.10.0 + */ + private static boolean isNativeAdventureSupport() { + if (nativeAdventureSupport == null) { + try { + Component component = Component.text("test"); + NativeComponentHolder holder = new NativeComponentHolder(component); + + //If NoSuchMethodError or something is thrown we can assume that + //Adventure components are not natively supported by the server platform + + //noinspection unused + Object ignored1 = holder.asInventoryTitle(null, 9); + //noinspection unused + Object ignored2 = holder.asInventoryTitle(null, InventoryType.HOPPER); + + ItemMeta meta = new ItemStack(Material.STONE).getItemMeta(); + holder.asItemDisplayName(meta); + holder.asItemLoreAtEnd(meta); + + nativeAdventureSupport = true; + } catch (Throwable t) { + nativeAdventureSupport = false; + } + } + return nativeAdventureSupport; + } + + /** + * Gets the serializer to use when converting wrapped values to legacy strings. + * Main use case being the implementation of {@link #asLegacyString()}. + * + * @return a serializer for converting wrapped values to legacy strings + * @since 0.10.0 + */ + private static LegacyComponentSerializer getLegacySerializer() { + if (legacySerializer == null) { + LegacyComponentSerializer.Builder builder = LegacyComponentSerializer.builder() + .character(LegacyComponentSerializer.SECTION_CHAR); + if (!net.md_5.bungee.api.ChatColor.class.isEnum()) { + //1.16+ Spigot (or Paper), hex colors are supported, no need to down sample them + builder.hexColors() + .useUnusualXRepeatedCharacterHexFormat(); + } + legacySerializer = builder.build(); + } + return legacySerializer; + } + + /** + * The Adventure component this instance wraps. + */ + @NotNull + protected final Component value; + + /** + * Creates and initializes a new instance. + * + * @param value the Adventure component this instance should wrap + * @since 0.10.0 + */ + ComponentHolder(@NotNull Component value) { + this.value = value; + } + + /** + * Gets the Adventure component this instance wraps. + * + * @return the contained Adventure component + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public Component getComponent() { + return value; + } + + /** + * Gets the wrapped Adventure component in a JSON representation. + * + * @return the contained Adventure component as JSON + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public JsonElement asJson() { + return GsonComponentSerializer.gson().serializeToTree(value); + } + + @NotNull + @Contract(pure = true) + @Override + public String toString() { + return getClass().getSimpleName() + "{" + value + "}"; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other != null && getClass() == other.getClass() + && Objects.equals(value, ((ComponentHolder) other).value); + } + + @NotNull + @Contract(pure = true) + @Override + public String asLegacyString() { + return getLegacySerializer().serialize(value); + } +} diff --git a/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ForeignComponentHolder.java b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ForeignComponentHolder.java new file mode 100644 index 000000000..ddc7955e9 --- /dev/null +++ b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/ForeignComponentHolder.java @@ -0,0 +1,70 @@ +package com.github.stefvanschie.inventoryframework.adventuresupport; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * A {@link ComponentHolder} implementation for platforms where Adventure isn't natively supported. + * Adventure components are converted to legacy Strings before passed to the Bukkit API. + * + * @see NativeComponentHolder + * @since 0.10.0 + */ +class ForeignComponentHolder extends ComponentHolder { + + /** + * A {@link StringHolder} wrapping {@link #asLegacyString()}. + * This class depends on {@link StringHolder} to reduce code duplication. + */ + @NotNull + private final StringHolder legacy; + + /** + * Creates and initializes a new instance. + * + * @param value the Adventure component this instance should wrap + * @since 0.10.0 + */ + ForeignComponentHolder(@NotNull Component value) { + super(value); + legacy = StringHolder.of(asLegacyString()); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, InventoryType type) { + return legacy.asInventoryTitle(holder, type); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, int size) { + return legacy.asInventoryTitle(holder, size); + } + + @NotNull + @Contract(pure = true) + @Override + public Merchant asMerchantTitle() { + return legacy.asMerchantTitle(); + } + + @Override + public void asItemDisplayName(ItemMeta meta) { + legacy.asItemDisplayName(meta); + } + + @Override + public void asItemLoreAtEnd(ItemMeta meta) { + legacy.asItemLoreAtEnd(meta); + } +} diff --git a/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/NativeComponentHolder.java b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/NativeComponentHolder.java new file mode 100644 index 000000000..eaea94d11 --- /dev/null +++ b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/NativeComponentHolder.java @@ -0,0 +1,70 @@ +package com.github.stefvanschie.inventoryframework.adventuresupport; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A {@link ComponentHolder} implementation for platforms where Adventure is natively supported. + * Adventure components are directly passed to the Bukkit (Paper) API. + * + * @see ForeignComponentHolder + * @since 0.10.0 + */ +class NativeComponentHolder extends ComponentHolder { + + /** + * Creates and initializes a new instance. + * + * @param value the Adventure component this instance should wrap + * @since 0.10.0 + */ + NativeComponentHolder(@NotNull Component value) { + super(value); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, InventoryType type) { + return Bukkit.createInventory(holder, type, value); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, int size) { + return Bukkit.createInventory(holder, size, value); + } + + @NotNull + @Contract(pure = true) + @Override + public Merchant asMerchantTitle() { + return Bukkit.createMerchant(value); + } + + @Override + public void asItemDisplayName(ItemMeta meta) { + meta.displayName(value); + } + + @Override + public void asItemLoreAtEnd(ItemMeta meta) { + List lore = meta.hasLore() + ? Objects.requireNonNull(meta.lore()) + : new ArrayList<>(); + lore.add(value); + meta.lore(lore); + } +} diff --git a/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/StringHolder.java b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/StringHolder.java new file mode 100644 index 000000000..cfbbb7aa6 --- /dev/null +++ b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/StringHolder.java @@ -0,0 +1,138 @@ +package com.github.stefvanschie.inventoryframework.adventuresupport; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Wrapper of a legacy string value. + * {@link org.bukkit.ChatColor} based formatting is used. + * + * @since 0.10.0 + */ +public final class StringHolder extends TextHolder { + + /** + * Cached instance which wraps an empty {@link String}. + */ + @NotNull + private static final StringHolder EMPTY = StringHolder.of(""); + + /** + * Wraps the specified legacy string. + * + * @param value the value to wrap + * @return an instance that wraps the specified value + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static StringHolder of(@NotNull String value) { + Validate.notNull(value, "value mustn't be null"); + return new StringHolder(value); + } + + /** + * Gets an instance that contains no characters. + * + * @return an instance without any characters + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static StringHolder empty() { + return EMPTY; + } + + /** + * The legacy string this instance wraps. + */ + @NotNull + private final String value; + + /** + * Creates and initializes a new instance. + * + * @param value the legacy string this instance should wrap + * @since 0.10.0 + */ + private StringHolder(@NotNull String value) { + this.value = value; + } + + @NotNull + @Contract(pure = true) + @Override + public String toString() { + return getClass().getSimpleName() + "{" + value + "}"; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other != null && getClass() == other.getClass() + && Objects.equals(value, ((StringHolder) other).value); + } + + @NotNull + @Contract(pure = true) + @Override + public String asLegacyString() { + return value; + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, InventoryType type) { + //noinspection deprecation + return Bukkit.createInventory(holder, type, value); + } + + @NotNull + @Contract(pure = true) + @Override + public Inventory asInventoryTitle(InventoryHolder holder, int size) { + //noinspection deprecation + return Bukkit.createInventory(holder, size, value); + } + + @NotNull + @Contract(pure = true) + @Override + public Merchant asMerchantTitle() { + //noinspection deprecation + return Bukkit.createMerchant(value); + } + + @Override + public void asItemDisplayName(ItemMeta meta) { + //noinspection deprecation + meta.setDisplayName(value); + } + + @Override + public void asItemLoreAtEnd(ItemMeta meta) { + //noinspection deprecation + List lore = meta.hasLore() + ? Objects.requireNonNull(meta.getLore()) + : new ArrayList<>(); + lore.add(value); + //noinspection deprecation + meta.setLore(lore); + } +} diff --git a/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/TextHolder.java b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/TextHolder.java new file mode 100644 index 000000000..dea4b8d72 --- /dev/null +++ b/adventure-support/src/main/java/com/github/stefvanschie/inventoryframework/adventuresupport/TextHolder.java @@ -0,0 +1,132 @@ +package com.github.stefvanschie.inventoryframework.adventuresupport; + +import net.kyori.adventure.text.Component; +import org.bukkit.ChatColor; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Immutable wrapper of a text-like value. + * Support for both Adventure and legacy strings is achieved through this class. + * To get an instance of this class please refer to either {@link StringHolder#of(String)} + * or {@link ComponentHolder#of(Component)}. + * Other methods like {@link #empty()} and {@link #deserialize(String)} + * also exist, but their use cases are very limited. + * + * @see StringHolder + * @see ComponentHolder + * @since 0.10.0 + */ +public abstract class TextHolder { + + /** + * Gets an instance that contains no characters and no formatting. + * + * @return an instance without any characters or formatting + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static TextHolder empty() { + return StringHolder.empty(); + } + + /** + * Deserializes the specified {@link String} as a {@link TextHolder}. + * This method is still WIP and may change drastically in the future: + *
    + *
  • Are we going to use MiniMessage if it's present?
  • + *
  • Is MiniMessage going to be opt-in? If yes, how do we opt-in?
  • + *
+ * + * @param string the raw data to deserialize + * @return an instance containing the text from the string + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public static TextHolder deserialize(@NotNull String string) { + return StringHolder.of(ChatColor.translateAlternateColorCodes('&', string)); + } + + TextHolder() { + //package-private constructor to "seal" the class + } + + @NotNull + @Contract(pure = true) + @Override + public abstract String toString(); + + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object other); + + /** + * Converts the text wrapped by this class instance to a legacy string, + * keeping the original formatting. + * + * @return the wrapped value represented as a legacy string + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public abstract String asLegacyString(); + + /** + * Creates a new inventory with the wrapped value as the inventory's title. + * + * @param holder the holder to use for the new inventory + * @param type the type of inventory to create + * @return a newly created inventory with the wrapped value as its title + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public abstract Inventory asInventoryTitle(InventoryHolder holder, InventoryType type); + + /** + * Creates a new inventory with the wrapped value as the inventory's title. + * + * @param holder the holder to use for the new inventory + * @param size the count of slots the inventory should have (normal size restrictions apply) + * @return a newly created inventory with the wrapped value as its title + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public abstract Inventory asInventoryTitle(InventoryHolder holder, int size); + + /** + * Creates a new merchant with the wrapped value as the merchant's title. + * + * @return a newly created inventory with the wrapped value as its title + * @since 0.10.0 + */ + @NotNull + @Contract(pure = true) + public abstract Merchant asMerchantTitle(); + + /** + * Modifies the specified meta: sets the display name to the wrapped value. + * + * @param meta the meta whose display name to set + * @since 0.10.0 + */ + public abstract void asItemDisplayName(ItemMeta meta); + + /** + * Modifies the specified meta: adds the wrapped value as a new lore line at the end + * + * @param meta the meta whose lore to append to + * @since 0.10.0 + */ + public abstract void asItemLoreAtEnd(ItemMeta meta); +} diff --git a/nms/1_16_R1/pom.xml b/inventory-view/iv-abstract-class/pom.xml similarity index 55% rename from nms/1_16_R1/pom.xml rename to inventory-view/iv-abstract-class/pom.xml index 47e02eab6..9638b29e7 100644 --- a/nms/1_16_R1/pom.xml +++ b/inventory-view/iv-abstract-class/pom.xml @@ -2,32 +2,46 @@ + 4.0.0 - IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + IF-parent + 0.12.0-SNAPSHOT ../../pom.xml - 4.0.0 - 1_16_R1 + iv-abstract-class true + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + - - org.spigotmc - spigot - 1.16.1-R0.1-SNAPSHOT - provided - com.github.stefvanschie.inventoryframework - abstraction + iv-abstraction ${project.version} - compile + + + org.spigotmc + spigot-api + 1.20.6-R0.1-SNAPSHOT + provided + + + + org.apache.commons + commons-lang3 + + + \ No newline at end of file diff --git a/inventory-view/iv-abstract-class/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstractclass/InventoryViewUtil.java b/inventory-view/iv-abstract-class/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstractclass/InventoryViewUtil.java new file mode 100644 index 000000000..eb8ec2084 --- /dev/null +++ b/inventory-view/iv-abstract-class/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstractclass/InventoryViewUtil.java @@ -0,0 +1,77 @@ +package com.github.stefvanschie.inventoryframework.inventoryview.abstractclass; + +import com.github.stefvanschie.inventoryframework.inventoryview.abstraction.AbstractInventoryViewUtil; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper for {@link InventoryView} methods that apply when {@link InventoryView} was an abstract class. + * + * @since 0.10.16 + */ +public class InventoryViewUtil implements AbstractInventoryViewUtil { + + /** + * Instance of this singleton class. + */ + @NotNull + private static final InventoryViewUtil INSTANCE = new InventoryViewUtil(); + + @NotNull + @Override + public Inventory getBottomInventory(@NotNull InventoryView view) { + return view.getBottomInventory(); + } + + @Nullable + @Override + public ItemStack getCursor(@NotNull InventoryView view) { + return view.getCursor(); + } + + @Override + public void setCursor(@NotNull InventoryView view, @Nullable ItemStack item) { + view.setCursor(item); + } + + @Nullable + @Override + public Inventory getInventory(@NotNull InventoryView view, int slot) { + return view.getInventory(slot); + } + + @NotNull + @Override + public InventoryType.SlotType getSlotType(@NotNull InventoryView view, int slot) { + return view.getSlotType(slot); + } + + @NotNull + @Override + public String getTitle(@NotNull InventoryView view) { + return view.getTitle(); + } + + @NotNull + @Override + public Inventory getTopInventory(@NotNull InventoryView view) { + return view.getTopInventory(); + } + + /** + * Gets the singleton instance of this class. + * + * @return the instance of this class + * @since 0.10.16 + */ + @NotNull + @Contract(pure = true) + public static InventoryViewUtil getInstance() { + return INSTANCE; + } +} diff --git a/nms/1_16_R2/pom.xml b/inventory-view/iv-abstraction/pom.xml similarity index 53% rename from nms/1_16_R2/pom.xml rename to inventory-view/iv-abstraction/pom.xml index f630a7f1e..3746dbad5 100644 --- a/nms/1_16_R2/pom.xml +++ b/inventory-view/iv-abstraction/pom.xml @@ -2,32 +2,41 @@ + 4.0.0 - IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + IF-parent + 0.12.0-SNAPSHOT ../../pom.xml - 4.0.0 - 1_16_R2 + iv-abstraction true + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + org.spigotmc - spigot - 1.16.3-R0.1-SNAPSHOT + spigot-api + 1.21-R0.1-SNAPSHOT provided - - - com.github.stefvanschie.inventoryframework - abstraction - ${project.version} - compile + + + + org.apache.commons + commons-lang3 + + + \ No newline at end of file diff --git a/inventory-view/iv-abstraction/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstraction/AbstractInventoryViewUtil.java b/inventory-view/iv-abstraction/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstraction/AbstractInventoryViewUtil.java new file mode 100644 index 000000000..8d7f0c6cc --- /dev/null +++ b/inventory-view/iv-abstraction/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/abstraction/AbstractInventoryViewUtil.java @@ -0,0 +1,87 @@ +package com.github.stefvanschie.inventoryframework.inventoryview.abstraction; + +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper for {@link InventoryView} methods that apply when {@link InventoryView} was an abstract class. + * + * @since 0.10.16 + */ +public interface AbstractInventoryViewUtil { + + /** + * Behaves according to {@link InventoryView#getBottomInventory()}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getBottomInventory()} on + * @return the result of invoking {@link InventoryView#getBottomInventory()} + * @since 0.10.16 + */ + @NotNull + Inventory getBottomInventory(@NotNull InventoryView view); + + /** + * Behaves according to {@link InventoryView#getCursor()}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getCursor()} on + * @return the result of invoking {@link InventoryView#getCursor()} + * @since 0.10.16 + */ + @Nullable + ItemStack getCursor(@NotNull InventoryView view); + + /** + * Behaves according to {@link InventoryView#setCursor(ItemStack)}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#setCursor(ItemStack)} on + * @param item the {@link ItemStack} to apply when invoking {@link InventoryView#setCursor(ItemStack)} + * @since 0.10.16 + */ + void setCursor(@NotNull InventoryView view, @Nullable ItemStack item); + + /** + * Behaves according to {@link InventoryView#getInventory(int)}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getInventory(int)} on + * @param slot the slot to apply when invoking {@link InventoryView#getInventory(int)} + * @return the result of invoking {@link InventoryView#getInventory(int)} + * @since 0.10.16 + */ + @Nullable + Inventory getInventory(@NotNull InventoryView view, int slot); + + /** + * Behaves according to {@link InventoryView#getSlotType(int)}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getSlotType(int)} on + * @param slot the slot to apply when invoking {@link InventoryView#getSlotType(int)} + * @return the result of invoking {@link InventoryView#getSlotType(int)} + * @since 0.10.16 + */ + @NotNull + InventoryType.SlotType getSlotType(@NotNull InventoryView view, int slot); + + /** + * Behaves according to {@link InventoryView#getTitle()}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getTitle()} on + * @return the result of invoking {@link InventoryView#getTitle()} + * @since 0.10.16 + */ + @NotNull + String getTitle(@NotNull InventoryView view); + + /** + * Behaves according to {@link InventoryView#getTopInventory()}. + * + * @param view the {@link InventoryView} to invoke {@link InventoryView#getTopInventory()} on + * @return the result of invoking {@link InventoryView#getTopInventory()} + * @since 0.10.16 + */ + @NotNull + Inventory getTopInventory(@NotNull InventoryView view); +} diff --git a/nms/1_15_R1/pom.xml b/inventory-view/iv-interface/pom.xml similarity index 56% rename from nms/1_15_R1/pom.xml rename to inventory-view/iv-interface/pom.xml index 8cbdc5417..19ea6d9f4 100644 --- a/nms/1_15_R1/pom.xml +++ b/inventory-view/iv-interface/pom.xml @@ -2,32 +2,46 @@ + 4.0.0 - IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + IF-parent + 0.12.0-SNAPSHOT ../../pom.xml - 4.0.0 - 1_15_R1 + iv-interface true + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + - - org.spigotmc - spigot - 1.15.2-R0.1-SNAPSHOT - provided - com.github.stefvanschie.inventoryframework - abstraction + iv-abstraction ${project.version} - compile + + + org.spigotmc + spigot-api + 1.21-R0.1-SNAPSHOT + provided + + + + org.apache.commons + commons-lang3 + + + \ No newline at end of file diff --git a/inventory-view/iv-interface/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/interface_/InventoryViewUtil.java b/inventory-view/iv-interface/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/interface_/InventoryViewUtil.java new file mode 100644 index 000000000..9d94062a0 --- /dev/null +++ b/inventory-view/iv-interface/src/main/java/com/github/stefvanschie/inventoryframework/inventoryview/interface_/InventoryViewUtil.java @@ -0,0 +1,77 @@ +package com.github.stefvanschie.inventoryframework.inventoryview.interface_; + +import com.github.stefvanschie.inventoryframework.inventoryview.abstraction.AbstractInventoryViewUtil; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper for {@link InventoryView} methods that apply when {@link InventoryView} was an abstract class. + * + * @since 0.10.16 + */ +public class InventoryViewUtil implements AbstractInventoryViewUtil { + + /** + * Instance of this singleton class. + */ + @NotNull + private static final InventoryViewUtil INSTANCE = new InventoryViewUtil(); + + @NotNull + @Override + public Inventory getBottomInventory(@NotNull InventoryView view) { + return view.getBottomInventory(); + } + + @Nullable + @Override + public ItemStack getCursor(@NotNull InventoryView view) { + return view.getCursor(); + } + + @Override + public void setCursor(@NotNull InventoryView view, @Nullable ItemStack item) { + view.setCursor(item); + } + + @Nullable + @Override + public Inventory getInventory(@NotNull InventoryView view, int slot) { + return view.getInventory(slot); + } + + @NotNull + @Override + public InventoryType.SlotType getSlotType(@NotNull InventoryView view, int slot) { + return view.getSlotType(slot); + } + + @NotNull + @Override + public String getTitle(@NotNull InventoryView view) { + return view.getTitle(); + } + + @NotNull + @Override + public Inventory getTopInventory(@NotNull InventoryView view) { + return view.getTopInventory(); + } + + /** + * Gets the singleton instance of this class. + * + * @return the instance of this class + * @since 0.10.16 + */ + @NotNull + @Contract(pure = true) + public static InventoryViewUtil getInstance() { + return INSTANCE; + } +} diff --git a/nms/1_14_R1/pom.xml b/nms/1_14_R1/pom.xml deleted file mode 100644 index 6dbcde799..000000000 --- a/nms/1_14_R1/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - IF-parent - com.github.stefvanschie.inventoryframework - 0.9.8 - ../../pom.xml - - 4.0.0 - - 1_14_R1 - - - true - - - - - org.spigotmc - spigot - 1.14.4-R0.1-SNAPSHOT - provided - - - com.github.stefvanschie.inventoryframework - abstraction - ${project.version} - compile - - - \ No newline at end of file diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/AnvilInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/AnvilInventoryImpl.java deleted file mode 100644 index a17945e39..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/AnvilInventoryImpl.java +++ /dev/null @@ -1,304 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryAnvil; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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 anvil inventory for 1.14 R1 - * - * @since 0.8.0 - */ -public class AnvilInventoryImpl extends AnvilInventory { - - public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerAnvil; - - int id = containerAnvil.windowId; - ChatMessage message = new ChatMessage(title); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.ANVIL, message)); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.a); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container anvil for responding to item renaming - * - * @since 0.8.0 - */ - private class ContainerAnvilImpl extends ContainerAnvil { - - /** - * The player for whom this anvil container is - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container anvil - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the repair inventory field - */ - @NotNull - private final Field repairInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - /** - * Field for accessing the container access field - */ - @NotNull - private final Field containerAccessField; - - /** - * Creates a new custom anvil container for the specified player - * - * @param entityPlayer the player for who this anvil container is - * @since 0.8.0 - */ - public ContainerAnvilImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - try { - repairInventoryField = ContainerAnvil.class.getDeclaredField("repairInventory"); - repairInventoryField.setAccessible(true); - - resultInventoryField = ContainerAnvil.class.getDeclaredField("resultInventory"); - resultInventoryField.setAccessible(true); - - containerAccessField = ContainerAnvil.class.getDeclaredField("containerAccess"); - containerAccessField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getRepairInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getRepairInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - getResultInventory().setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - Location location = getContainerAccess().getLocation(); - CraftInventory inventory = new CraftInventoryAnvil(location, getRepairInventory(), getResultInventory(), - this) { - @NotNull - @Contract(pure = true) - @Override - public InventoryHolder getHolder() { - return inventoryHolder; - } - }; - - bukkitEntity = new CraftInventoryView(player, inventory, this); - } - - return bukkitEntity; - } - - @Override - public void a(@Nullable String name) { - text = name == null ? "" : name; - - sendResultItem(player, getResultInventory().getItem(0)); - } - - @Contract(pure = true, value = "_ -> true") - @Override - public boolean canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getRepairInventory() { - try { - return (IInventory) repairInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - @NotNull - @Contract(pure = true) - private ContainerAccess getContainerAccess() { - try { - return (ContainerAccess) containerAccessField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/BeaconInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/BeaconInventoryImpl.java deleted file mode 100644 index e6fb366da..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/BeaconInventoryImpl.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryBeacon; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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.14 R1 - * - * @since 0.8.0 - */ -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) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(entityPlayer, item); - - entityPlayer.activeContainer = containerBeacon; - - int id = containerBeacon.windowId; - ChatMessage message = new ChatMessage("Beacon"); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.BEACON, message)); - - sendItem(player, item); - } - - @Override - public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - NonNullList items = NonNullList.a( - ItemStack.a, //the first item doesn't count for some reason, so send a dummy item - CraftItemStack.asNMSCopy(item) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), items)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container beacon - * - * @since 0.8.0 - */ - private class ContainerBeaconImpl extends ContainerBeacon { - - /** - * 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 EntityPlayer entityPlayer, @Nullable org.bukkit.inventory.ItemStack item) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.beaconField = ContainerBeacon.class.getDeclaredField("beacon"); - this.beaconField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - ItemStack itemStack = CraftItemStack.asNMSCopy(item); - - ((IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/CartographyTableInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/CartographyTableInventoryImpl.java deleted file mode 100644 index 75766664d..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/CartographyTableInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryCartography; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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.14 R1 - * - * @since 0.8.0 - */ -public class CartographyTableInventoryImpl extends CartographyTableInventory { - - public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl( - entityPlayer, items - ); - - entityPlayer.activeContainer = containerCartographyTable; - - int id = containerCartographyTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.CARTOGRAPHY, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container cartography table - * - * @since 0.8.0 - */ - private class ContainerCartographyTableImpl extends ContainerCartography { - - /** - * 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 resultInventoryField; - - public ContainerCartographyTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerCartography.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - } -} diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/EnchantingTableInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/EnchantingTableInventoryImpl.java deleted file mode 100644 index 0acb73900..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/EnchantingTableInventoryImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryEnchanting; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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.14 R1 - * - * @since 0.8.0 - */ -public class EnchantingTableInventoryImpl extends EnchantingTableInventory { - - public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.ENCHANTMENT, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerEnchantingTableImpl extends ContainerEnchantTable { - - /** - * 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 EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.enchantSlotsField = ContainerEnchantTable.class.getDeclaredField("enchantSlots"); - this.enchantSlotsField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - IInventory input = (IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/GrindstoneInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/GrindstoneInventoryImpl.java deleted file mode 100644 index c93769a78..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/GrindstoneInventoryImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryGrindstone; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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 grindstone inventory for 1.14 R1 - * - * @since 0.8.0 - */ -public class GrindstoneInventoryImpl extends GrindstoneInventory { - - public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerGrindstone; - - int id = containerGrindstone.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.GRINDSTONE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container grindstone - * - * @since 0.8.0 - */ - private class ContainerGrindstoneImpl extends ContainerGrindstone { - - /** - * The player for this grindstone container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container grindstone - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the craft inventory field - */ - @NotNull - private final Field craftInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - public ContainerGrindstoneImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.craftInventoryField = ContainerGrindstone.class.getDeclaredField("craftInventory"); - this.craftInventoryField.setAccessible(true); - - this.resultInventoryField = ContainerGrindstone.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getCraftInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getCraftInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - - getResultInventory().setItem(2, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventoryGrindstone(getCraftInventory(), 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the craft inventory - * - * @return the craft inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getCraftInventory() { - try { - return (IInventory) craftInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/StonecutterInventoryImpl.java b/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/StonecutterInventoryImpl.java deleted file mode 100644 index a193a995d..000000000 --- a/nms/1_14_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_14_R1/StonecutterInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_14_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; -import net.minecraft.server.v1_14_R1.*; -import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryStonecutter; -import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_14_R1.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.14 R1 - * - * @since 0.8.0 - */ -public class StonecutterInventoryImpl extends StonecutterInventory { - - public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.STONECUTTER, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerStonecutterImpl extends ContainerStonecutter { - - /** - * 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 resultInventoryField; - - public ContainerStonecutterImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerStonecutter.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/AnvilInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/AnvilInventoryImpl.java deleted file mode 100644 index 2cbfc8c58..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/AnvilInventoryImpl.java +++ /dev/null @@ -1,304 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryAnvil; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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 anvil inventory for 1.15 R1 - * - * @since 0.8.0 - */ -public class AnvilInventoryImpl extends AnvilInventory { - - public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerAnvil; - - int id = containerAnvil.windowId; - ChatMessage message = new ChatMessage(title); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.ANVIL, message)); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.a); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container anvil for responding to item renaming - * - * @since 0.8.0 - */ - private class ContainerAnvilImpl extends ContainerAnvil { - - /** - * The player for whom this anvil container is - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container anvil - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the repair inventory field - */ - @NotNull - private final Field repairInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - /** - * Field for accessing the container access field - */ - @NotNull - private final Field containerAccessField; - - /** - * Creates a new custom anvil container for the specified player - * - * @param entityPlayer the player for who this anvil container is - * @since 0.8.0 - */ - public ContainerAnvilImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - try { - repairInventoryField = ContainerAnvil.class.getDeclaredField("repairInventory"); - repairInventoryField.setAccessible(true); - - resultInventoryField = ContainerAnvil.class.getDeclaredField("resultInventory"); - resultInventoryField.setAccessible(true); - - containerAccessField = ContainerAnvil.class.getDeclaredField("containerAccess"); - containerAccessField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getRepairInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getRepairInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - getResultInventory().setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - Location location = getContainerAccess().getLocation(); - CraftInventory inventory = new CraftInventoryAnvil(location, getRepairInventory(), getResultInventory(), - this) { - @NotNull - @Contract(pure = true) - @Override - public InventoryHolder getHolder() { - return inventoryHolder; - } - }; - - bukkitEntity = new CraftInventoryView(player, inventory, this); - } - - return bukkitEntity; - } - - @Override - public void a(@Nullable String name) { - text = name == null ? "" : name; - - sendResultItem(player, getResultInventory().getItem(0)); - } - - @Contract(pure = true, value = "_ -> true") - @Override - public boolean canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getRepairInventory() { - try { - return (IInventory) repairInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - @NotNull - @Contract(pure = true) - private ContainerAccess getContainerAccess() { - try { - return (ContainerAccess) containerAccessField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/BeaconInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/BeaconInventoryImpl.java deleted file mode 100644 index 38d7d21df..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/BeaconInventoryImpl.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryBeacon; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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.15 R1 - * - * @since 0.8.0 - */ -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) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(entityPlayer, item); - - entityPlayer.activeContainer = containerBeacon; - - int id = containerBeacon.windowId; - ChatMessage message = new ChatMessage("Beacon"); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.BEACON, message)); - - sendItem(player, item); - } - - @Override - public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - NonNullList items = NonNullList.a( - ItemStack.a, //the first item doesn't count for some reason, so send a dummy item - CraftItemStack.asNMSCopy(item) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), items)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container beacon - * - * @since 0.8.0 - */ - private class ContainerBeaconImpl extends ContainerBeacon { - - /** - * 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 EntityPlayer entityPlayer, @Nullable org.bukkit.inventory.ItemStack item) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.beaconField = ContainerBeacon.class.getDeclaredField("beacon"); - this.beaconField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - ItemStack itemStack = CraftItemStack.asNMSCopy(item); - - ((IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/CartographyTableInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/CartographyTableInventoryImpl.java deleted file mode 100644 index 9748a472a..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/CartographyTableInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryCartography; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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.15 R1 - * - * @since 0.8.0 - */ -public class CartographyTableInventoryImpl extends CartographyTableInventory { - - public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl( - entityPlayer, items - ); - - entityPlayer.activeContainer = containerCartographyTable; - - int id = containerCartographyTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.CARTOGRAPHY_TABLE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container cartography table - * - * @since 0.8.0 - */ - private class ContainerCartographyTableImpl extends ContainerCartography { - - /** - * 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 resultInventoryField; - - public ContainerCartographyTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerCartography.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/EnchantingTableInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/EnchantingTableInventoryImpl.java deleted file mode 100644 index 42018e6c9..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/EnchantingTableInventoryImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryEnchanting; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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.15 R1 - * - * @since 0.8.0 - */ -public class EnchantingTableInventoryImpl extends EnchantingTableInventory { - - public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.ENCHANTMENT, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerEnchantingTableImpl extends ContainerEnchantTable { - - /** - * 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 EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.enchantSlotsField = ContainerEnchantTable.class.getDeclaredField("enchantSlots"); - this.enchantSlotsField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - IInventory input = (IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/GrindstoneInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/GrindstoneInventoryImpl.java deleted file mode 100644 index d582f5fcc..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/GrindstoneInventoryImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryGrindstone; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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 grindstone inventory for 1.15 R1 - * - * @since 0.8.0 - */ -public class GrindstoneInventoryImpl extends GrindstoneInventory { - - public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerGrindstone; - - int id = containerGrindstone.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.GRINDSTONE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container grindstone - * - * @since 0.8.0 - */ - private class ContainerGrindstoneImpl extends ContainerGrindstone { - - /** - * The player for this grindstone container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container grindstone - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the craft inventory field - */ - @NotNull - private final Field craftInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - public ContainerGrindstoneImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.craftInventoryField = ContainerGrindstone.class.getDeclaredField("craftInventory"); - this.craftInventoryField.setAccessible(true); - - this.resultInventoryField = ContainerGrindstone.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getCraftInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getCraftInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - - getResultInventory().setItem(2, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventoryGrindstone(getCraftInventory(), 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the craft inventory - * - * @return the craft inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getCraftInventory() { - try { - return (IInventory) craftInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/StonecutterInventoryImpl.java b/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/StonecutterInventoryImpl.java deleted file mode 100644 index ded6bb0d1..000000000 --- a/nms/1_15_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_15_R1/StonecutterInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_15_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; -import net.minecraft.server.v1_15_R1.*; -import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryStonecutter; -import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_15_R1.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.15 R1 - * - * @since 0.8.0 - */ -public class StonecutterInventoryImpl extends StonecutterInventory { - - public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.STONECUTTER, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.a, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.a)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerStonecutterImpl extends ContainerStonecutter { - - /** - * 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 resultInventoryField; - - public ContainerStonecutterImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerStonecutter.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R3/pom.xml b/nms/1_16_5/pom.xml similarity index 81% rename from nms/1_16_R3/pom.xml rename to nms/1_16_5/pom.xml index 5ddb316fd..a70bbb7cb 100644 --- a/nms/1_16_R3/pom.xml +++ b/nms/1_16_5/pom.xml @@ -5,30 +5,30 @@ IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + 0.12.0-SNAPSHOT ../../pom.xml 4.0.0 - 1_16_R3 + 1_16_5 true + + io.papermc + paper + 1.16.5-R0.1-SNAPSHOT + provided + com.github.stefvanschie.inventoryframework abstraction ${project.version} compile - - org.spigotmc - spigot - 1.16.4-R0.1-SNAPSHOT - provided - \ No newline at end of file diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/AnvilInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/AnvilInventoryImpl.java new file mode 100644 index 000000000..bb1b28008 --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/AnvilInventoryImpl.java @@ -0,0 +1,296 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Container; +import net.minecraft.server.v1_16_R3.ContainerAccess; +import net.minecraft.server.v1_16_R3.ContainerAnvil; +import net.minecraft.server.v1_16_R3.ContainerProperty; +import net.minecraft.server.v1_16_R3.EntityHuman; +import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.IChatBaseComponent; +import net.minecraft.server.v1_16_R3.ICrafting; +import net.minecraft.server.v1_16_R3.IInventory; +import net.minecraft.server.v1_16_R3.ITileInventory; +import net.minecraft.server.v1_16_R3.InventoryClickType; +import net.minecraft.server.v1_16_R3.InventoryLargeChest; +import net.minecraft.server.v1_16_R3.InventorySubcontainer; +import net.minecraft.server.v1_16_R3.ItemStack; +import net.minecraft.server.v1_16_R3.PlayerInventory; +import net.minecraft.server.v1_16_R3.Slot; +import net.minecraft.server.v1_16_R3.World; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Internal anvil inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + InventorySubcontainer inputSlots = new InventorySubcontainer(2); + InventorySubcontainer resultSlot = new InventorySubcontainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public IInventory getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.8.0 + */ + private class ContainerAnvilImpl extends ContainerAnvil { + + /** + * The container for the input slots. + */ + @NotNull + private final InventorySubcontainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final InventorySubcontainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * The field containing the properties. + */ + @NotNull + private final Field propertiesField; + + /** + * The field containing the listeners. + */ + @NotNull + private final Field listenersField; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer inputSlots, + @NotNull InventorySubcontainer resultSlot + ) { + super(containerId, player.inventory, ContainerAccess.at(player.world, BlockPosition.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.levelCost.set(AnvilInventoryImpl.super.cost); + + InventoryLargeChest compoundContainer = new InventoryLargeChest(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + + try { + this.propertiesField = Container.class.getDeclaredField("d"); + this.propertiesField.setAccessible(true); + + this.listenersField = Container.class.getDeclaredField("listeners"); + this.listenersField.setAccessible(true); + } catch (NoSuchFieldException exception) { + throw new IllegalStateException(exception); + } + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.containerAccess.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void c() { + if (super.levelCost.c()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.items.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @Override + public void a(@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(); + } + + @Override + public ItemStack a(int index, int dragData, @NotNull InventoryClickType clickType, @NotNull EntityHuman player) { + ItemStack item = super.a(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + + return item; + } + + @Override + protected ItemStack a(@NotNull EntityHuman player, @NotNull ItemStack stack) { + return stack; + } + + @Override + public void a(@NotNull IInventory container) { + c(); + } + + @Override + public void e() {} + + @Override + public void b(@NotNull EntityHuman nmsPlayer) {} + + @Override + protected void a(@NotNull EntityHuman player, @NotNull World world, @NotNull IInventory inventory) {} + + /** + * Broadcasts the full menu state to the client. + * + * @since 0.11.0 + */ + private void broadcastFullState() { + List properties; + try { + //noinspection unchecked + properties = (List) this.propertiesField.get(this); + } catch (IllegalAccessException exception) { + throw new IllegalStateException(exception); + } + + for (int index = 0; index < properties.size(); index++) { + ContainerProperty property = properties.get(index); + + if (property.c()) { + try { + //noinspection unchecked + for (ICrafting listener : (List) this.listenersField.get(this)) { + listener.setContainerData(this, index, property.get()); + } + } catch (IllegalAccessException exception) { + throw new IllegalStateException(exception); + } + } + } + + if (super.player instanceof EntityPlayer) { + ((EntityPlayer) super.player).updateInventory(this); + } + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/BeaconInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/BeaconInventoryImpl.java new file mode 100644 index 000000000..103125c9e --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/BeaconInventoryImpl.java @@ -0,0 +1,164 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.ChatComponentText; +import net.minecraft.server.v1_16_R3.Container; +import net.minecraft.server.v1_16_R3.ContainerAccess; +import net.minecraft.server.v1_16_R3.ContainerBeacon; +import net.minecraft.server.v1_16_R3.ContainerProperties; +import net.minecraft.server.v1_16_R3.EntityHuman; +import net.minecraft.server.v1_16_R3.IChatBaseComponent; +import net.minecraft.server.v1_16_R3.IInventory; +import net.minecraft.server.v1_16_R3.ITileInventory; +import net.minecraft.server.v1_16_R3.InventorySubcontainer; +import net.minecraft.server.v1_16_R3.PlayerInventory; +import net.minecraft.server.v1_16_R3.Slot; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return new ChatComponentText("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.8.0 + */ + private static class ContainerBeaconImpl extends ContainerBeacon { + + /** + * The player viewing this menu. + */ + @NotNull + private final EntityHuman player; + + /** + * The container for the input slots. + */ + @NotNull + private final InventorySubcontainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer inputSlot + ) { + super(containerId, player.inventory, new ContainerProperties(3), + ContainerAccess.at(player.world, BlockPosition.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void b(@Nullable EntityHuman player) {} + + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/CartographyTableInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..0b03a6a3b --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/CartographyTableInventoryImpl.java @@ -0,0 +1,186 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + InventorySubcontainer resultSlot = new InventorySubcontainer(1); + + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.8.0 + */ + private static class ContainerCartographyTableImpl extends ContainerCartography { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final InventorySubcontainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final InventorySubcontainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer inputSlots, + @NotNull InventorySubcontainer resultSlot + ) { + super(containerId, player.inventory, ContainerAccess.at(player.getWorld(), BlockPosition.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + InventoryLargeChest container = new InventoryLargeChest(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void a(@Nullable IInventory container) {} + + @Override + public void b(@Nullable EntityHuman player) {} + + @Override + protected void a(@Nullable EntityHuman player, @Nullable World world, @Nullable IInventory container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/EnchantingTableInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..e7452058b --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/EnchantingTableInventoryImpl.java @@ -0,0 +1,171 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.8.0 + */ + private static class ContainerEnchantingTableImpl extends ContainerEnchantTable { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final InventorySubcontainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer inputSlots + ) { + super(containerId, player.inventory, ContainerAccess.at(player.getWorld(), BlockPosition.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void a(@Nullable IInventory container) {} + + @Override + public void b(@Nullable EntityHuman player) {} + + @Override + protected void a(@Nullable EntityHuman player, @Nullable World world, @Nullable IInventory container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/GrindstoneInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..c26cd3546 --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/GrindstoneInventoryImpl.java @@ -0,0 +1,185 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + InventorySubcontainer resultSlot = new InventorySubcontainer(1); + + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.8.0 + */ + private static class ContainerGrindstoneImpl extends ContainerGrindstone { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final InventorySubcontainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final InventorySubcontainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer itemsSlots, + @NotNull InventorySubcontainer resultSlot + ) { + super(containerId, player.inventory, ContainerAccess.at(player.getWorld(), BlockPosition.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + InventoryLargeChest container = new InventoryLargeChest(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void a(@Nullable IInventory container) {} + + @Override + public void b(@Nullable EntityHuman player) {} + + @Override + protected void a(@Nullable EntityHuman player, @Nullable World world, @Nullable IInventory container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/MerchantInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/MerchantInventoryImpl.java new file mode 100644 index 000000000..4486f2073 --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/MerchantInventoryImpl.java @@ -0,0 +1,301 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.Container; +import net.minecraft.server.v1_16_R3.ContainerMerchant; +import net.minecraft.server.v1_16_R3.EntityHuman; +import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.IChatBaseComponent; +import net.minecraft.server.v1_16_R3.IInventory; +import net.minecraft.server.v1_16_R3.IMerchant; +import net.minecraft.server.v1_16_R3.ITileInventory; +import net.minecraft.server.v1_16_R3.InventoryMerchant; +import net.minecraft.server.v1_16_R3.MerchantRecipeList; +import net.minecraft.server.v1_16_R3.MerchantWrapper; +import net.minecraft.server.v1_16_R3.PlayerInventory; +import net.minecraft.server.v1_16_R3.Slot; +import net.minecraft.server.v1_16_R3.World; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.16.4 - 1.16.5 + * + * @since 0.10.1 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + IMerchant merchant = new MerchantWrapper(null); + + InventoryMerchant container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public InventoryMerchant getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantRecipeList offers = new MerchantRecipeList(); + + for (Map.Entry 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.server.v1_16_R3.ItemStack nmsItemA = CraftItemStack.asNMSCopy(itemA); + net.minecraft.server.v1_16_R3.ItemStack nmsItemB = net.minecraft.server.v1_16_R3.ItemStack.b; + net.minecraft.server.v1_16_R3.ItemStack nmsItemResult = CraftItemStack.asNMSCopy(recipe.getResult()); + + if (itemB != null) { + nmsItemB = CraftItemStack.asNMSCopy(itemB); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + net.minecraft.server.v1_16_R3.MerchantRecipe merchantOffer = new net.minecraft.server.v1_16_R3.MerchantRecipe( + nmsItemA, nmsItemB, nmsItemResult, uses, maxUses, exp, multiplier + ); + merchantOffer.setSpecialPrice(entry.getValue()); + + offers.add(merchantOffer); + } + + EntityPlayer entityPlayer = getEntityPlayer(player); + + entityPlayer.openTrade(getWindowId(entityPlayer), offers, level, experience, true, false); + } + + /** + * Gets the entity player associated to this player + * + * @param player the player to get the entity player from + * @return the entity player + * @since 0.10.1 + */ + @NotNull + @Contract(pure = true) + private EntityPlayer getEntityPlayer(@NotNull Player player) { + return ((CraftPlayer) player).getHandle(); + } + + /** + * Gets the window id for the inventory view the player currently has open + * + * @param entityPlayer the player to get the window id for + * @return the window id + * @since 0.10.1 + */ + @Contract(pure = true) + private int getWindowId(@NotNull EntityPlayer entityPlayer) { + return entityPlayer.activeContainer.windowId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventoryMerchant implements ITileInventory { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull IMerchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends ContainerMerchant { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final InventoryMerchant container; + + /** + * The merchant. + */ + @NotNull + private final IMerchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventoryMerchant container, + @NotNull IMerchant merchant + ) { + super(containerId, player.inventory, merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean isAllowed(@Nullable EntityHuman player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean isAllowed(@Nullable net.minecraft.server.v1_16_R3.ItemStack itemStack) { + return false; + } + }; + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void a(@Nullable IInventory container) {} + + @Override + public void b(@Nullable EntityHuman player) {} + + @Override + protected void a(@Nullable EntityHuman player, @Nullable World world, @Nullable IInventory container) {} + + @Override + public void d(int i) {} + + @Override + public void g(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/SmithingTableInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..5fc9b1e6d --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/SmithingTableInventoryImpl.java @@ -0,0 +1,206 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + InventoryCraftResult resultSlot = new InventoryCraftResult(); + + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container smithing table + * + * @since 0.8.0 + */ + private static class ContainerSmithingTableImpl extends ContainerSmithing { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final InventorySubcontainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final InventoryCraftResult resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer itemsSlots, + @NotNull InventoryCraftResult resultSlot + ) { + super(containerId, player.inventory, ContainerAccess.at(player.getWorld(), BlockPosition.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + InventoryLargeChest container = new InventoryLargeChest(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + super.containerAccess.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Contract(pure = true, value = "_ -> true") + @Override + public boolean canUse(@Nullable EntityHuman nmsPlayer) { + return true; + } + + @Override + public void a(IInventory container) {} + + @Override + public void b(EntityHuman nmsPlayer) {} + + @Override + public void e() {} + + @Override + protected ItemStack a(EntityHuman player, ItemStack stack) { + return stack; + } + + @Override + protected boolean b(EntityHuman player, boolean present) { + return true; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/StonecutterInventoryImpl.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/StonecutterInventoryImpl.java new file mode 100644 index 000000000..56547dfaa --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/StonecutterInventoryImpl.java @@ -0,0 +1,193 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_16_5.util.TextHolderUtil; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.16 R3 + * + * @since 0.8.0 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + InventorySubcontainer resultSlot = new InventorySubcontainer(1); + + IInventory container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public Container createMenu( + int containerId, + @Nullable PlayerInventory inventory, + @NotNull EntityHuman player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public IChatBaseComponent getScoreboardDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public IInventory getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends InventorySubcontainer implements ITileInventory { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.8.0 + */ + private static class ContainerStonecutterImpl extends ContainerStonecutter { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final InventorySubcontainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final InventorySubcontainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull EntityHuman player, + @NotNull InventorySubcontainer inputSlot, + @NotNull InventorySubcontainer resultSlot + ) { + super(containerId, player.inventory, ContainerAccess.at(player.getWorld(), BlockPosition.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + InventoryLargeChest container = new InventoryLargeChest(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Contract(pure = true, value = "_ -> true") + @Override + public boolean canUse(@Nullable EntityHuman nmsPlayer) { + return true; + } + + @Override + public void a(IInventory container) {} + + @Override + public void b(EntityHuman nmsPlayer) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean a(@Nullable EntityHuman player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull IInventory container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.index, slot.e, slot.f); + newSlot.rawSlotIndex = slot.rawSlotIndex; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/util/TextHolderUtil.java b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/util/TextHolderUtil.java new file mode 100644 index 000000000..bd1c3a999 --- /dev/null +++ b/nms/1_16_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_5/util/TextHolderUtil.java @@ -0,0 +1,66 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_16_5.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.server.v1_16_R3.ChatComponentText; +import net.minecraft.server.v1_16_R3.IChatBaseComponent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.0 + */ +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.0 + */ + @NotNull + @Contract(pure = true) + public static IChatBaseComponent 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.0 + */ + @NotNull + @Contract(pure = true) + private static IChatBaseComponent toComponent(@NotNull StringHolder holder) { + return new ChatComponentText(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.0 + */ + @NotNull + @Contract(pure = true) + private static IChatBaseComponent toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(IChatBaseComponent.ChatSerializer.a(holder.asJson())); + } +} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/AnvilInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/AnvilInventoryImpl.java deleted file mode 100644 index 5da3e3f29..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/AnvilInventoryImpl.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryAnvil; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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; - -/** - * Internal anvil inventory for 1.16 R1 - * - * @since 0.8.0 - */ -public class AnvilInventoryImpl extends AnvilInventory { - - public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerAnvil; - - int id = containerAnvil.windowId; - ChatMessage message = new ChatMessage(title); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.ANVIL, message)); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container anvil for responding to item renaming - * - * @since 0.8.0 - */ - private class ContainerAnvilImpl extends ContainerAnvil { - - /** - * The player for whom this anvil container is - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container anvil - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Creates a new custom anvil container for the specified player - * - * @param entityPlayer the player for who this anvil container is - * @since 0.8.0 - */ - public ContainerAnvilImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - Location location = containerAccess.getLocation(); - CraftInventory inventory = new CraftInventoryAnvil(location, repairInventory, resultInventory, - this) { - @NotNull - @Contract(pure = true) - @Override - public InventoryHolder getHolder() { - return inventoryHolder; - } - }; - - bukkitEntity = new CraftInventoryView(player, inventory, this); - } - - return bukkitEntity; - } - - @Override - public void a(@Nullable String name) { - text = name == null ? "" : name; - - sendResultItem(player, resultInventory.getItem(0)); - } - - @Contract(pure = true, value = "_ -> true") - @Override - public boolean canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/BeaconInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/BeaconInventoryImpl.java deleted file mode 100644 index 3bfc1f68b..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/BeaconInventoryImpl.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryBeacon; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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.16 R1 - * - * @since 0.8.0 - */ -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) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(entityPlayer, item); - - entityPlayer.activeContainer = containerBeacon; - - int id = containerBeacon.windowId; - ChatMessage message = new ChatMessage("Beacon"); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.BEACON, message)); - - sendItem(player, item); - } - - @Override - public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - NonNullList items = NonNullList.a( - ItemStack.b, //the first item doesn't count for some reason, so send a dummy item - CraftItemStack.asNMSCopy(item) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), items)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container beacon - * - * @since 0.8.0 - */ - private class ContainerBeaconImpl extends ContainerBeacon { - - /** - * 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 EntityPlayer entityPlayer, @Nullable org.bukkit.inventory.ItemStack item) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.beaconField = ContainerBeacon.class.getDeclaredField("beacon"); - this.beaconField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - ItemStack itemStack = CraftItemStack.asNMSCopy(item); - - ((IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/CartographyTableInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/CartographyTableInventoryImpl.java deleted file mode 100644 index e4d3895a0..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/CartographyTableInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryCartography; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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.16 R1 - * - * @since 0.8.0 - */ -public class CartographyTableInventoryImpl extends CartographyTableInventory { - - public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl( - entityPlayer, items - ); - - entityPlayer.activeContainer = containerCartographyTable; - - int id = containerCartographyTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.CARTOGRAPHY_TABLE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container cartography table - * - * @since 0.8.0 - */ - private class ContainerCartographyTableImpl extends ContainerCartography { - - /** - * 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 resultInventoryField; - - public ContainerCartographyTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerCartography.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/EnchantingTableInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/EnchantingTableInventoryImpl.java deleted file mode 100644 index 901cceb5e..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/EnchantingTableInventoryImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryEnchanting; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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.16 R1 - * - * @since 0.8.0 - */ -public class EnchantingTableInventoryImpl extends EnchantingTableInventory { - - public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.ENCHANTMENT, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerEnchantingTableImpl extends ContainerEnchantTable { - - /** - * 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 EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.enchantSlotsField = ContainerEnchantTable.class.getDeclaredField("enchantSlots"); - this.enchantSlotsField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - IInventory input = (IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/GrindstoneInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/GrindstoneInventoryImpl.java deleted file mode 100644 index 291db39c1..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/GrindstoneInventoryImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryGrindstone; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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 grindstone inventory for 1.16 R1 - * - * @since 0.8.0 - */ -public class GrindstoneInventoryImpl extends GrindstoneInventory { - - public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerGrindstone; - - int id = containerGrindstone.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.GRINDSTONE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container grindstone - * - * @since 0.8.0 - */ - private class ContainerGrindstoneImpl extends ContainerGrindstone { - - /** - * The player for this grindstone container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container grindstone - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the craft inventory field - */ - @NotNull - private final Field craftInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - public ContainerGrindstoneImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.craftInventoryField = ContainerGrindstone.class.getDeclaredField("craftInventory"); - this.craftInventoryField.setAccessible(true); - - this.resultInventoryField = ContainerGrindstone.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getCraftInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getCraftInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - - getResultInventory().setItem(2, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventoryGrindstone(getCraftInventory(), 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the craft inventory - * - * @return the craft inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getCraftInventory() { - try { - return (IInventory) craftInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/SmithingTableInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/SmithingTableInventoryImpl.java deleted file mode 100644 index c9e30f97c..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/SmithingTableInventoryImpl.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventorySmithing; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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; - -/** - * Internal smithing table inventory for 1.16 R1 - * - * @since 0.8.0 - */ -public class SmithingTableInventoryImpl extends SmithingTableInventory { - - public SmithingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String title, - @Nullable org.bukkit.inventory.ItemStack[] items) { - int itemAmount = items.length; - - if (itemAmount != 3) { - throw new IllegalArgumentException( - "The amount of items for a stonecutter should be 3, but is '" + itemAmount + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerSmithingTableImpl containerSmithingTable = new ContainerSmithingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerSmithingTable; - - int id = containerSmithingTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.SMITHING, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container smithing table - * - * @since 0.8.0 - */ - private class ContainerSmithingTableImpl extends ContainerSmithing { - - /** - * The player for this smithing table container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container smithing table - */ - @Nullable - private CraftInventoryView bukkitEntity; - - public ContainerSmithingTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventorySmithing(repairInventory, resultInventory) { - @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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/StonecutterInventoryImpl.java b/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/StonecutterInventoryImpl.java deleted file mode 100644 index 8c76fb843..000000000 --- a/nms/1_16_R1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R1/StonecutterInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R1; - -import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; -import net.minecraft.server.v1_16_R1.*; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryStonecutter; -import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R1.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.16 R1 - * - * @since 0.8.0 - */ -public class StonecutterInventoryImpl extends StonecutterInventory { - - public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.STONECUTTER, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerStonecutterImpl extends ContainerStonecutter { - - /** - * 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 resultInventoryField; - - public ContainerStonecutterImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerStonecutter.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/AnvilInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/AnvilInventoryImpl.java deleted file mode 100644 index b0fc4bd74..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/AnvilInventoryImpl.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftInventoryAnvil; -import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R2.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; - -/** - * Internal anvil inventory for 1.16 R2 - * - * @since 0.8.0 - */ -public class AnvilInventoryImpl extends AnvilInventory { - - public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerAnvil; - - int id = containerAnvil.windowId; - ChatMessage message = new ChatMessage(title); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.ANVIL, message)); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container anvil for responding to item renaming - * - * @since 0.8.0 - */ - private class ContainerAnvilImpl extends ContainerAnvil { - - /** - * The player for whom this anvil container is - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container anvil - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Creates a new custom anvil container for the specified player - * - * @param entityPlayer the player for who this anvil container is - * @since 0.8.0 - */ - public ContainerAnvilImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - Location location = containerAccess.getLocation(); - CraftInventory inventory = new CraftInventoryAnvil(location, repairInventory, resultInventory, - this) { - @NotNull - @Contract(pure = true) - @Override - public InventoryHolder getHolder() { - return inventoryHolder; - } - }; - - bukkitEntity = new CraftInventoryView(player, inventory, this); - } - - return bukkitEntity; - } - - @Override - public void a(@Nullable String name) { - text = name == null ? "" : name; - - sendResultItem(player, resultInventory.getItem(0)); - } - - @Contract(pure = true, value = "_ -> true") - @Override - public boolean canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/BeaconInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/BeaconInventoryImpl.java deleted file mode 100644 index 2a8d3cc64..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/BeaconInventoryImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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.16 R2 - * - * @since 0.8.0 - */ -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) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(entityPlayer, item); - - entityPlayer.activeContainer = containerBeacon; - - int id = containerBeacon.windowId; - ChatMessage message = new ChatMessage("Beacon"); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.BEACON, message)); - - sendItem(player, item); - } - - @Override - public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - NonNullList items = NonNullList.a( - ItemStack.b, //the first item doesn't count for some reason, so send a dummy item - CraftItemStack.asNMSCopy(item) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), items)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container beacon - * - * @since 0.8.0 - */ - private class ContainerBeaconImpl extends ContainerBeacon { - - /** - * 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 EntityPlayer entityPlayer, @Nullable org.bukkit.inventory.ItemStack item) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.beaconField = ContainerBeacon.class.getDeclaredField("beacon"); - this.beaconField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - ItemStack itemStack = CraftItemStack.asNMSCopy(item); - - ((IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/CartographyTableInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/CartographyTableInventoryImpl.java deleted file mode 100644 index b11ac0f23..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/CartographyTableInventoryImpl.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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.16 R2 - * - * @since 0.8.0 - */ -public class CartographyTableInventoryImpl extends CartographyTableInventory { - - public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl( - entityPlayer, items - ); - - entityPlayer.activeContainer = containerCartographyTable; - - int id = containerCartographyTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.CARTOGRAPHY_TABLE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container cartography table - * - * @since 0.8.0 - */ - private class ContainerCartographyTableImpl extends ContainerCartography { - - /** - * 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 resultInventoryField; - - public ContainerCartographyTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerCartography.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/EnchantingTableInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/EnchantingTableInventoryImpl.java deleted file mode 100644 index 89145689c..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/EnchantingTableInventoryImpl.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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.16 R2 - * - * @since 0.8.0 - */ -public class EnchantingTableInventoryImpl extends EnchantingTableInventory { - - public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.ENCHANTMENT, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerEnchantingTableImpl extends ContainerEnchantTable { - - /** - * 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 EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.enchantSlotsField = ContainerEnchantTable.class.getDeclaredField("enchantSlots"); - this.enchantSlotsField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - IInventory input = (IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/GrindstoneInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/GrindstoneInventoryImpl.java deleted file mode 100644 index 48bf2f02e..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/GrindstoneInventoryImpl.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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 grindstone inventory for 1.16 R2 - * - * @since 0.8.0 - */ -public class GrindstoneInventoryImpl extends GrindstoneInventory { - - public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerGrindstone; - - int id = containerGrindstone.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.GRINDSTONE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container grindstone - * - * @since 0.8.0 - */ - private class ContainerGrindstoneImpl extends ContainerGrindstone { - - /** - * The player for this grindstone container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container grindstone - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the craft inventory field - */ - @NotNull - private final Field craftInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - public ContainerGrindstoneImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.craftInventoryField = ContainerGrindstone.class.getDeclaredField("craftInventory"); - this.craftInventoryField.setAccessible(true); - - this.resultInventoryField = ContainerGrindstone.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getCraftInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getCraftInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - - getResultInventory().setItem(2, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventoryGrindstone(getCraftInventory(), 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the craft inventory - * - * @return the craft inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getCraftInventory() { - try { - return (IInventory) craftInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/SmithingTableInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/SmithingTableInventoryImpl.java deleted file mode 100644 index 82779d966..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/SmithingTableInventoryImpl.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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; - -/** - * Internal smithing table inventory for 1.16 R2 - * - * @since 0.8.0 - */ -public class SmithingTableInventoryImpl extends SmithingTableInventory { - - public SmithingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String title, - @Nullable org.bukkit.inventory.ItemStack[] items) { - int itemAmount = items.length; - - if (itemAmount != 3) { - throw new IllegalArgumentException( - "The amount of items for a stonecutter should be 3, but is '" + itemAmount + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerSmithingTableImpl containerSmithingTable = new ContainerSmithingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerSmithingTable; - - int id = containerSmithingTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.SMITHING, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container smithing table - * - * @since 0.8.0 - */ - private class ContainerSmithingTableImpl extends ContainerSmithing { - - /** - * The player for this smithing table container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container smithing table - */ - @Nullable - private CraftInventoryView bukkitEntity; - - public ContainerSmithingTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventorySmithing(containerAccess.getLocation(), repairInventory, - resultInventory) { - @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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/StonecutterInventoryImpl.java b/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/StonecutterInventoryImpl.java deleted file mode 100644 index 83d8d88d4..000000000 --- a/nms/1_16_R2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R2/StonecutterInventoryImpl.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R2; - -import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; -import net.minecraft.server.v1_16_R2.*; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R2.inventory.*; -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.16 R2 - * - * @since 0.8.0 - */ -public class StonecutterInventoryImpl extends StonecutterInventory { - - public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.STONECUTTER, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerStonecutterImpl extends ContainerStonecutter { - - /** - * 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 resultInventoryField; - - public ContainerStonecutterImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerStonecutter.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/AnvilInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/AnvilInventoryImpl.java deleted file mode 100644 index 636d6ce51..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/AnvilInventoryImpl.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryAnvil; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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; - -/** - * Internal anvil inventory for 1.16 R3 - * - * @since 0.8.0 - */ -public class AnvilInventoryImpl extends AnvilInventory { - - public AnvilInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerAnvilImpl containerAnvil = new ContainerAnvilImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerAnvil; - - int id = containerAnvil.windowId; - ChatMessage message = new ChatMessage(title); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.ANVIL, message)); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container anvil for responding to item renaming - * - * @since 0.8.0 - */ - private class ContainerAnvilImpl extends ContainerAnvil { - - /** - * The player for whom this anvil container is - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container anvil - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Creates a new custom anvil container for the specified player - * - * @param entityPlayer the player for who this anvil container is - * @since 0.8.0 - */ - public ContainerAnvilImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - Location location = containerAccess.getLocation(); - CraftInventory inventory = new CraftInventoryAnvil(location, repairInventory, resultInventory, - this) { - @NotNull - @Contract(pure = true) - @Override - public InventoryHolder getHolder() { - return inventoryHolder; - } - }; - - bukkitEntity = new CraftInventoryView(player, inventory, this); - } - - return bukkitEntity; - } - - @Override - public void a(@Nullable String name) { - text = name == null ? "" : name; - - sendResultItem(player, resultInventory.getItem(0)); - } - - @Contract(pure = true, value = "_ -> true") - @Override - public boolean canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/BeaconInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/BeaconInventoryImpl.java deleted file mode 100644 index b1a433ab3..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/BeaconInventoryImpl.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryBeacon; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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.16 R3 - * - * @since 0.8.0 - */ -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) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerBeaconImpl containerBeacon = new ContainerBeaconImpl(entityPlayer, item); - - entityPlayer.activeContainer = containerBeacon; - - int id = containerBeacon.windowId; - ChatMessage message = new ChatMessage("Beacon"); - - entityPlayer.playerConnection.sendPacket(new PacketPlayOutOpenWindow(id, Containers.BEACON, message)); - - sendItem(player, item); - } - - @Override - public void sendItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - NonNullList items = NonNullList.a( - ItemStack.b, //the first item doesn't count for some reason, so send a dummy item - CraftItemStack.asNMSCopy(item) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), items)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container beacon - * - * @since 0.8.0 - */ - private class ContainerBeaconImpl extends ContainerBeacon { - - /** - * 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 EntityPlayer entityPlayer, @Nullable org.bukkit.inventory.ItemStack item) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.beaconField = ContainerBeacon.class.getDeclaredField("beacon"); - this.beaconField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - ItemStack itemStack = CraftItemStack.asNMSCopy(item); - - ((IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/CartographyTableInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/CartographyTableInventoryImpl.java deleted file mode 100644 index e63aba2f3..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/CartographyTableInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryCartography; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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.16 R3 - * - * @since 0.8.0 - */ -public class CartographyTableInventoryImpl extends CartographyTableInventory { - - public CartographyTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerCartographyTableImpl containerCartographyTable = new ContainerCartographyTableImpl( - entityPlayer, items - ); - - entityPlayer.activeContainer = containerCartographyTable; - - int id = containerCartographyTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.CARTOGRAPHY_TABLE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container cartography table - * - * @since 0.8.0 - */ - private class ContainerCartographyTableImpl extends ContainerCartography { - - /** - * 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 resultInventoryField; - - public ContainerCartographyTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerCartography.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/EnchantingTableInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/EnchantingTableInventoryImpl.java deleted file mode 100644 index 7c3584325..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/EnchantingTableInventoryImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryEnchanting; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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.16 R3 - * - * @since 0.8.0 - */ -public class EnchantingTableInventoryImpl extends EnchantingTableInventory { - - public EnchantingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerEnchantingTableImpl containerEnchantmentTable = new ContainerEnchantingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.ENCHANTMENT, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerEnchantingTableImpl extends ContainerEnchantTable { - - /** - * 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 EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.enchantSlotsField = ContainerEnchantTable.class.getDeclaredField("enchantSlots"); - this.enchantSlotsField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - try { - IInventory input = (IInventory) 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((IInventory) 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/GrindstoneInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/GrindstoneInventoryImpl.java deleted file mode 100644 index 140cf4d7b..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/GrindstoneInventoryImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryGrindstone; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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 grindstone inventory for 1.16 R3 - * - * @since 0.8.0 - */ -public class GrindstoneInventoryImpl extends GrindstoneInventory { - - public GrindstoneInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerGrindstoneImpl containerGrindstone = new ContainerGrindstoneImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerGrindstone; - - int id = containerGrindstone.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.GRINDSTONE, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container grindstone - * - * @since 0.8.0 - */ - private class ContainerGrindstoneImpl extends ContainerGrindstone { - - /** - * The player for this grindstone container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container grindstone - */ - @Nullable - private CraftInventoryView bukkitEntity; - - /** - * Field for accessing the craft inventory field - */ - @NotNull - private final Field craftInventoryField; - - /** - * Field for accessing the result inventory field - */ - @NotNull - private final Field resultInventoryField; - - public ContainerGrindstoneImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.craftInventoryField = ContainerGrindstone.class.getDeclaredField("craftInventory"); - this.craftInventoryField.setAccessible(true); - - this.resultInventoryField = ContainerGrindstone.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - getCraftInventory().setItem(0, CraftItemStack.asNMSCopy(items[0])); - getCraftInventory().setItem(1, CraftItemStack.asNMSCopy(items[1])); - - getResultInventory().setItem(2, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventoryGrindstone(getCraftInventory(), 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the craft inventory - * - * @return the craft inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getCraftInventory() { - try { - return (IInventory) craftInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/SmithingTableInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/SmithingTableInventoryImpl.java deleted file mode 100644 index a9e9e5d60..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/SmithingTableInventoryImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventorySmithing; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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; - -/** - * Internal smithing table inventory for 1.16 R3 - * - * @since 0.8.0 - */ -public class SmithingTableInventoryImpl extends SmithingTableInventory { - - public SmithingTableInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String title, - @Nullable org.bukkit.inventory.ItemStack[] items) { - int itemAmount = items.length; - - if (itemAmount != 3) { - throw new IllegalArgumentException( - "The amount of items for a smithing table should be 3, but is '" + itemAmount + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerSmithingTableImpl containerSmithingTable = new ContainerSmithingTableImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerSmithingTable; - - int id = containerSmithingTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.SMITHING, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]), - CraftItemStack.asNMSCopy(items[2]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void sendFirstItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 0, nmsItem)); - } - - @Override - public void sendSecondItem(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 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.b); - } - - @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) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * 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.8.0 - */ - private void setCursor(@NotNull Player player, @NotNull ItemStack item) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -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.8.0 - */ - private void sendResultItem(@NotNull Player player, @NotNull ItemStack item) { - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutSetSlot(getWindowId(entityPlayer), 2, item)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container smithing table - * - * @since 0.8.0 - */ - private class ContainerSmithingTableImpl extends ContainerSmithing { - - /** - * The player for this smithing table container - */ - @NotNull - private final Player player; - - /** - * The internal bukkit entity for this container smithing table - */ - @Nullable - private CraftInventoryView bukkitEntity; - - public ContainerSmithingTableImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory, - ContainerAccess.at(entityPlayer.getWorld(), new BlockPosition(0, 0, 0))); - - this.player = entityPlayer.getBukkitEntity(); - - repairInventory.setItem(0, CraftItemStack.asNMSCopy(items[0])); - repairInventory.setItem(1, CraftItemStack.asNMSCopy(items[1])); - resultInventory.setItem(0, CraftItemStack.asNMSCopy(items[2])); - } - - @NotNull - @Override - public CraftInventoryView getBukkitView() { - if (bukkitEntity == null) { - CraftInventory inventory = new CraftInventorySmithing(containerAccess.getLocation(), repairInventory, - resultInventory) { - @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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - } -} diff --git a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/StonecutterInventoryImpl.java b/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/StonecutterInventoryImpl.java deleted file mode 100644 index 2e9db691e..000000000 --- a/nms/1_16_R3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_16_R3/StonecutterInventoryImpl.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.github.stefvanschie.inventoryframework.nms.v1_16_R3; - -import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; -import net.minecraft.server.v1_16_R3.*; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventory; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryStonecutter; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftInventoryView; -import org.bukkit.craftbukkit.v1_16_R3.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.16 R3 - * - * @since 0.8.0 - */ -public class StonecutterInventoryImpl extends StonecutterInventory { - - public StonecutterInventoryImpl(@NotNull InventoryHolder inventoryHolder) { - super(inventoryHolder); - } - - @Override - public void openInventory(@NotNull Player player, @NotNull String 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 + "'" - ); - } - - EntityPlayer entityPlayer = getEntityPlayer(player); - ContainerStonecutterImpl containerEnchantmentTable = new ContainerStonecutterImpl(entityPlayer, items); - - entityPlayer.activeContainer = containerEnchantmentTable; - - int id = containerEnchantmentTable.windowId; - ChatMessage message = new ChatMessage(title); - PacketPlayOutOpenWindow packet = new PacketPlayOutOpenWindow(id, Containers.STONECUTTER, message); - - entityPlayer.playerConnection.sendPacket(packet); - - sendItems(player, items); - } - - @Override - public void sendItems(@NotNull Player player, @Nullable org.bukkit.inventory.ItemStack[] items) { - NonNullList nmsItems = NonNullList.a( - ItemStack.b, - CraftItemStack.asNMSCopy(items[0]), - CraftItemStack.asNMSCopy(items[1]) - ); - - EntityPlayer entityPlayer = getEntityPlayer(player); - - getPlayerConnection(entityPlayer).sendPacket(new PacketPlayOutWindowItems(getWindowId(entityPlayer), nmsItems)); - } - - @Override - public void clearCursor(@NotNull Player player) { - getPlayerConnection(getEntityPlayer(player)).sendPacket(new PacketPlayOutSetSlot(-1, -1, ItemStack.b)); - } - - /** - * Gets the window id for the inventory view the player currently has open - * - * @param entityPlayer the player to get the window id for - * @return the window id - * @since 0.8.0 - */ - @Contract(pure = true) - private int getWindowId(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.activeContainer.windowId; - } - - /** - * Gets the player connection for the specified player - * - * @param entityPlayer the player to get the player connection from - * @return the player connection - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private PlayerConnection getPlayerConnection(@NotNull EntityPlayer entityPlayer) { - return entityPlayer.playerConnection; - } - - /** - * Gets the entity player associated to this player - * - * @param player the player to get the entity player from - * @return the entity player - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - private EntityPlayer getEntityPlayer(@NotNull Player player) { - return ((CraftPlayer) player).getHandle(); - } - - /** - * A custom container enchanting table - * - * @since 0.8.0 - */ - private class ContainerStonecutterImpl extends ContainerStonecutter { - - /** - * 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 resultInventoryField; - - public ContainerStonecutterImpl(@NotNull EntityPlayer entityPlayer, - @Nullable org.bukkit.inventory.ItemStack[] items) { - super(entityPlayer.nextContainerCounter(), entityPlayer.inventory); - - this.player = entityPlayer.getBukkitEntity(); - - try { - this.resultInventoryField = ContainerStonecutter.class.getDeclaredField("resultInventory"); - this.resultInventoryField.setAccessible(true); - } catch (NoSuchFieldException exception) { - throw new RuntimeException(exception); - } - - inventory.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.inventory, 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 canUse(@Nullable EntityHuman entityhuman) { - return true; - } - - @Override - public void a(IInventory inventory) {} - - @Override - public void b(EntityHuman entityhuman) {} - - /** - * Gets the result inventory - * - * @return the result inventory - * @since 0.8.0 - */ - @NotNull - @Contract(pure = true) - public IInventory getResultInventory() { - try { - return (IInventory) resultInventoryField.get(this); - } catch (IllegalAccessException exception) { - throw new RuntimeException(exception); - } - } - } -} diff --git a/nms/1_17_1/pom.xml b/nms/1_17_1/pom.xml new file mode 100644 index 000000000..6eab7f160 --- /dev/null +++ b/nms/1_17_1/pom.xml @@ -0,0 +1,59 @@ + + + + IF-parent + com.github.stefvanschie.inventoryframework + 0.12.0-SNAPSHOT + ../../pom.xml + + 4.0.0 + + 1_17_1 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.17.1-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + + \ No newline at end of file diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/AnvilInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/AnvilInventoryImpl.java new file mode 100644 index 000000000..b514a51e9 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.0 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @Override + public void 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(); + } + + @Override + public void slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/BeaconInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/BeaconInventoryImpl.java new file mode 100644 index 000000000..4ea869e26 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/BeaconInventoryImpl.java @@ -0,0 +1,162 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return new TextComponent("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.0 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/CartographyTableInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..2c4f73965 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.0 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/EnchantingTableInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..10fa539c4 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.0 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/GrindstoneInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..1fcb75659 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.0 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/MerchantInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/MerchantInventoryImpl.java new file mode 100644 index 000000000..2cd7e9da2 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.17.1 + * + * @since 0.10.1 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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.1 + */ + @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.1 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/SmithingTableInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..786f3ed21 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/SmithingTableInventoryImpl.java @@ -0,0 +1,216 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.0 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/StonecutterInventoryImpl.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/StonecutterInventoryImpl.java new file mode 100644 index 000000000..db6007ef1 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_17_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.17 R1 + * + * @since 0.10.0 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.0 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/CustomInventoryUtil.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/CustomInventoryUtil.java new file mode 100644 index 000000000..4959cb8b6 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_17_R1.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.0 + */ +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.0 + */ + @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_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/TextHolderUtil.java b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/TextHolderUtil.java new file mode 100644 index 000000000..9615b0b52 --- /dev/null +++ b/nms/1_17_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_17_1/util/TextHolderUtil.java @@ -0,0 +1,66 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_17_1.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.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.0 + */ +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.0 + */ + @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.0 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull StringHolder holder) { + return new TextComponent(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.0 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson())); + } +} diff --git a/nms/1_18_2/pom.xml b/nms/1_18_2/pom.xml new file mode 100644 index 000000000..48d5cd5be --- /dev/null +++ b/nms/1_18_2/pom.xml @@ -0,0 +1,59 @@ + + + + IF-parent + com.github.stefvanschie.inventoryframework + 0.12.0-SNAPSHOT + ../../pom.xml + + 4.0.0 + + 1_18_2 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.18.2-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + + \ No newline at end of file diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/AnvilInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/AnvilInventoryImpl.java new file mode 100644 index 000000000..d4dde5e37 --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.5 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.getLevel(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @Override + public void 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(); + } + + @Override + public void slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/BeaconInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/BeaconInventoryImpl.java new file mode 100644 index 000000000..083a8d68a --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/BeaconInventoryImpl.java @@ -0,0 +1,162 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return new TextComponent("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.5 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/CartographyTableInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..43e04c2cf --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.5 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/EnchantingTableInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..afb6c25d5 --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.5 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/GrindstoneInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..0a708b49e --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.5 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/MerchantInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/MerchantInventoryImpl.java new file mode 100644 index 000000000..a7a729401 --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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.5 + */ + @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.5 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/SmithingTableInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..2d5c1cbaa --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/SmithingTableInventoryImpl.java @@ -0,0 +1,216 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.5 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/StonecutterInventoryImpl.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/StonecutterInventoryImpl.java new file mode 100644 index 000000000..a574ad1c5 --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_18_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.18.2 + * + * @since 0.10.5 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.5 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/CustomInventoryUtil.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/CustomInventoryUtil.java new file mode 100644 index 000000000..1f20cc3ca --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_18_R2.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.5 + */ +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.5 + */ + @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_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/TextHolderUtil.java b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/TextHolderUtil.java new file mode 100644 index 000000000..2e2fe9542 --- /dev/null +++ b/nms/1_18_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_18_2/util/TextHolderUtil.java @@ -0,0 +1,66 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_18_2.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.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.5 + */ +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.5 + */ + @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.5 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull StringHolder holder) { + return new TextComponent(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.5 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson())); + } +} diff --git a/nms/1_19_4/pom.xml b/nms/1_19_4/pom.xml new file mode 100644 index 000000000..9719f1edc --- /dev/null +++ b/nms/1_19_4/pom.xml @@ -0,0 +1,58 @@ + + + + IF-parent + com.github.stefvanschie.inventoryframework + 0.12.0-SNAPSHOT + ../../pom.xml + + 4.0.0 + + 1_19_4 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.19.4-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + \ No newline at end of file diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/AnvilInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/AnvilInventoryImpl.java new file mode 100644 index 000000000..f73f294bf --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.9 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.getLevel(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @Override + public void 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(); + } + + @Override + public void slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/BeaconInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/BeaconInventoryImpl.java new file mode 100644 index 000000000..1a4357b4f --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.9 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/CartographyTableInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..ff23cf36b --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.9 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/EnchantingTableInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..373598e97 --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.9 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/GrindstoneInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..665123b2b --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.9 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/LegacySmithingTableInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/LegacySmithingTableInventoryImpl.java new file mode 100644 index 000000000..d7779127c --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/LegacySmithingTableInventoryImpl.java @@ -0,0 +1,218 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.LegacySmithingMenu; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.19.4. This smithing table is for prior to Minecraft 1.20. + * + * @since 0.10.9 + * @deprecated this type of smithing table will be removed in Minecraft 1.20 + */ +@Deprecated +public class LegacySmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.9 + */ + private static class ContainerSmithingTableImpl extends LegacySmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/MerchantInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/MerchantInventoryImpl.java new file mode 100644 index 000000000..b366305b7 --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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.9 + */ + @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.9 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/SmithingTableInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..6684733eb --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/SmithingTableInventoryImpl.java @@ -0,0 +1,218 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventorySmithingNew; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.19.4. This smithing table is for Minecraft 1.19.4 or higher. + * + * @since 0.10.9 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING_NEW; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.9 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithingNew( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/StonecutterInventoryImpl.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/StonecutterInventoryImpl.java new file mode 100644 index 000000000..bf902671a --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_19_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.19.4 + * + * @since 0.10.9 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.9 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level, BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/CustomInventoryUtil.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/CustomInventoryUtil.java new file mode 100644 index 000000000..317736273 --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_19_R3.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. + */ +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.9 + */ + @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_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/TextHolderUtil.java b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/TextHolderUtil.java new file mode 100644 index 000000000..bf999b34d --- /dev/null +++ b/nms/1_19_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_19_4/util/TextHolderUtil.java @@ -0,0 +1,65 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_19_4.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.network.chat.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.9 + */ +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.9 + */ + @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.9 + */ + @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.9 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson())); + } +} diff --git a/nms/1_20_0/pom.xml b/nms/1_20_0/pom.xml new file mode 100644 index 000000000..40dffc488 --- /dev/null +++ b/nms/1_20_0/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_20_0 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.20-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + \ No newline at end of file diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/AnvilInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/AnvilInventoryImpl.java new file mode 100644 index 000000000..b6e06ce66 --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.14 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/BeaconInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/BeaconInventoryImpl.java new file mode 100644 index 000000000..771895278 --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.14 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/CartographyTableInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..96434ae6c --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.14 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/EnchantingTableInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..a724028ac --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/GrindstoneInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..02f1c311b --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.14 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/MerchantInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/MerchantInventoryImpl.java new file mode 100644 index 000000000..42eb7f999 --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/SmithingTableInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..a5b1302cf --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.20.0. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.14 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.14 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/StonecutterInventoryImpl.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/StonecutterInventoryImpl.java new file mode 100644 index 000000000..c913ba35c --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.0 + * + * @since 0.10.14 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/CustomInventoryUtil.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/CustomInventoryUtil.java new file mode 100644 index 000000000..5050a7885 --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.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_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/TextHolderUtil.java b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/TextHolderUtil.java new file mode 100644 index 000000000..ef64afcc5 --- /dev/null +++ b/nms/1_20_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_0/util/TextHolderUtil.java @@ -0,0 +1,65 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_0.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.network.chat.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * 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())); + } +} diff --git a/nms/1_20_1/pom.xml b/nms/1_20_1/pom.xml new file mode 100644 index 000000000..0071336a1 --- /dev/null +++ b/nms/1_20_1/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_20_1 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.20.1-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + \ No newline at end of file diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/AnvilInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/AnvilInventoryImpl.java new file mode 100644 index 000000000..bcae7b1c0 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.14 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/BeaconInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/BeaconInventoryImpl.java new file mode 100644 index 000000000..c459d5954 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.14 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/CartographyTableInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..d853bd474 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.14 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/EnchantingTableInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..7c4f91890 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/GrindstoneInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..3ef8e6fe9 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.14 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/MerchantInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/MerchantInventoryImpl.java new file mode 100644 index 000000000..431ac5052 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/SmithingTableInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..ab26b15d8 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.20.1. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.14 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.14 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/StonecutterInventoryImpl.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/StonecutterInventoryImpl.java new file mode 100644 index 000000000..55fd065bc --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.1 + * + * @since 0.10.14 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/CustomInventoryUtil.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/CustomInventoryUtil.java new file mode 100644 index 000000000..ed08ff8f4 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R1.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_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/TextHolderUtil.java b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/TextHolderUtil.java new file mode 100644 index 000000000..3438bb279 --- /dev/null +++ b/nms/1_20_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_1/util/TextHolderUtil.java @@ -0,0 +1,65 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_1.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.network.chat.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * 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())); + } +} diff --git a/nms/1_20_2/pom.xml b/nms/1_20_2/pom.xml new file mode 100644 index 000000000..a3860e7a0 --- /dev/null +++ b/nms/1_20_2/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_20_2 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.20.2-SNAPSHOT + provided + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + + \ No newline at end of file diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/AnvilInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/AnvilInventoryImpl.java new file mode 100644 index 000000000..55b63dc95 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.12 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull net.minecraft.world.entity.player.Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull net.minecraft.world.entity.player.Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull net.minecraft.world.entity.player.Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/BeaconInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/BeaconInventoryImpl.java new file mode 100644 index 000000000..2bac23c86 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.12 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/CartographyTableInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..c5cb9074e --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.12 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/EnchantingTableInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..03cfd35a5 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.12 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/GrindstoneInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..71f44aa6d --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.12 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/MerchantInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/MerchantInventoryImpl.java new file mode 100644 index 000000000..93549473e --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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.12 + */ + @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.12 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/SmithingTableInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..91afd5ea7 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.20.2. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.12 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.12 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/StonecutterInventoryImpl.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/StonecutterInventoryImpl.java new file mode 100644 index 000000000..20d2b5a48 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_2.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.2 + * + * @since 0.10.12 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.12 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/CustomInventoryUtil.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/CustomInventoryUtil.java new file mode 100644 index 000000000..66f18dbff --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R2.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.12 + */ +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.12 + */ + @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_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/TextHolderUtil.java b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/TextHolderUtil.java new file mode 100644 index 000000000..d1bc6fe98 --- /dev/null +++ b/nms/1_20_2/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_2/util/TextHolderUtil.java @@ -0,0 +1,65 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_2.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.network.chat.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.12 + */ +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.12 + */ + @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.12 + */ + @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.12 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson())); + } +} diff --git a/nms/1_20_3-4/pom.xml b/nms/1_20_3-4/pom.xml new file mode 100644 index 000000000..967d44d19 --- /dev/null +++ b/nms/1_20_3-4/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_20_3-4 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + ca.bkaw + paper-nms + 1.20.3-SNAPSHOT + provided + + + + + + + + net.kyori + adventure-api + 4.25.0 + + + net.kyori + adventure-key + 4.26.1 + + + net.kyori + adventure-text-logger-slf4j + 4.26.1 + + + net.kyori + adventure-text-minimessage + 4.25.0 + + + net.kyori + adventure-text-serializer-ansi + 4.26.1 + + + net.kyori + adventure-text-serializer-gson + 4.25.0 + + + net.kyori + adventure-text-serializer-json + 4.25.0 + + + net.kyori + adventure-text-serializer-legacy + 4.26.1 + + + net.kyori + adventure-text-serializer-plain + 4.25.0 + + + + + + + + ca.bkaw + paper-nms-maven-plugin + 1.4.10 + + + process-classes + + remap + + + + + + + + + + bytecode.space + https://repo.bytecode.space/repository/maven-public/ + + + + \ No newline at end of file diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/AnvilInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/AnvilInventoryImpl.java new file mode 100644 index 000000000..a410aa854 --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.13 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(@NotNull Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, @NotNull ClickType clickType, @NotNull Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/BeaconInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/BeaconInventoryImpl.java new file mode 100644 index 000000000..472ce0f3f --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.13 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/CartographyTableInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..675ca6dec --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.13 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/EnchantingTableInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..429714477 --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.13 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/GrindstoneInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..eaf2cc0dd --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.13 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/MerchantInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/MerchantInventoryImpl.java new file mode 100644 index 000000000..c4a2ef53b --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/MerchantInventoryImpl.java @@ -0,0 +1,300 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Internal merchant inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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); + } + + int uses = recipe.getUses(); + int maxUses = recipe.getMaxUses(); + int exp = recipe.getVillagerExperience(); + float multiplier = recipe.getPriceMultiplier(); + + MerchantOffer merchantOffer = new MerchantOffer( + nmsItemA, nmsItemB, 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); + } + + /** + * 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.13 + */ + @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.13 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/SmithingTableInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..e7499c4e3 --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.20.3. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.13 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.13 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/StonecutterInventoryImpl.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/StonecutterInventoryImpl.java new file mode 100644 index 000000000..2a845182c --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.3 + * + * @since 0.10.13 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.13 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/CustomInventoryUtil.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/CustomInventoryUtil.java new file mode 100644 index 000000000..3a6cdb8d1 --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R3.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.13 + */ +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.13 + */ + @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_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/TextHolderUtil.java b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/TextHolderUtil.java new file mode 100644 index 000000000..f5cbc26ee --- /dev/null +++ b/nms/1_20_3-4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_3/util/TextHolderUtil.java @@ -0,0 +1,65 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_3.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.network.chat.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.10.13 + */ +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.13 + */ + @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.13 + */ + @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.13 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + return Objects.requireNonNull(Component.Serializer.fromJson(holder.asJson())); + } +} diff --git a/nms/1_20_5/pom.xml b/nms/1_20_5/pom.xml new file mode 100644 index 000000000..d107d2f37 --- /dev/null +++ b/nms/1_20_5/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_20_5 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.20.5-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.20.5-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.20.5-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.5-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.20.5-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/AnvilInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/AnvilInventoryImpl.java new file mode 100644 index 000000000..55df10daa --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.14 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, net.minecraft.world.entity.player.Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull net.minecraft.world.entity.player.Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull net.minecraft.world.entity.player.Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/BeaconInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/BeaconInventoryImpl.java new file mode 100644 index 000000000..88a260767 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.14 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/CartographyTableInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..55786c33e --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.14 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/EnchantingTableInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..9502dc374 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/GrindstoneInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..0484b7080 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.14 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/MerchantInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/MerchantInventoryImpl.java new file mode 100644 index 000000000..3f0d98e5a --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +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.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/SmithingTableInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..7019d6f9b --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +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.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.20.5. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.14 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.14 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/StonecutterInventoryImpl.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/StonecutterInventoryImpl.java new file mode 100644 index 000000000..8326ad8a4 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.5 + * + * @since 0.10.14 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/CustomInventoryUtil.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/CustomInventoryUtil.java new file mode 100644 index 000000000..b735c1655 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5.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_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/TextHolderUtil.java b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/TextHolderUtil.java new file mode 100644 index 000000000..9f8bce785 --- /dev/null +++ b/nms/1_20_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_5/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_5.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/nms/1_20_6/pom.xml b/nms/1_20_6/pom.xml new file mode 100644 index 000000000..192a9e33d --- /dev/null +++ b/nms/1_20_6/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-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.4 + + + 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 000000000..b0bee3a19 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +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.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +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 { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot, null) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.14 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot, + this + ); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..e64a6f1a9 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_6; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.20.6 + * + * @since 0.10.14 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.14 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} 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 000000000..4ce31ad87 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +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.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.20.6 + * + * @since 0.10.14 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.14 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} 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 000000000..7ef6ad8c8 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +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.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.20.6 + * + * @since 0.10.14 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..663de70de --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +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.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +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 { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.14 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..72649306d --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_20_6; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_20_6.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +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.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +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 { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..524ff44a5 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +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.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +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.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +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 { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.14 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..f50008ba5 --- /dev/null +++ b/nms/1_20_6/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_20_6/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +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.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.20.6 + * + * @since 0.10.14 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.14 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} 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 000000000..a0f724f18 --- /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 000000000..8e945b8bb --- /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/nms/1_21_0/pom.xml b/nms/1_21_0/pom.xml new file mode 100644 index 000000000..4ae97038c --- /dev/null +++ b/nms/1_21_0/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_0 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21-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.21-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/AnvilInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/AnvilInventoryImpl.java new file mode 100644 index 000000000..e69e59418 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.18 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/BeaconInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/BeaconInventoryImpl.java new file mode 100644 index 000000000..1f9c5b897 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.18 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/CartographyTableInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..a47c86266 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.18 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/EnchantingTableInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..8aa8d9a50 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/GrindstoneInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..d9bdf6165 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.18 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/MerchantInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/MerchantInventoryImpl.java new file mode 100644 index 000000000..d46b41ad5 --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.18 + */ + @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.18 + */ + @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.18 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/SmithingTableInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..973a338ca --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.0. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.18 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.18 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/StonecutterInventoryImpl.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/StonecutterInventoryImpl.java new file mode 100644 index 000000000..95ffce2bc --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_0.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.0 + * + * @since 0.10.18 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/CustomInventoryUtil.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/CustomInventoryUtil.java new file mode 100644 index 000000000..364fd9bca --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.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.18 + */ +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.18 + */ + @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_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/TextHolderUtil.java b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/TextHolderUtil.java new file mode 100644 index 000000000..b0757a95b --- /dev/null +++ b/nms/1_21_0/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_0/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_0.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.18 + */ +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.18 + */ + @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.18 + */ + @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.18 + */ + @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/nms/1_21_1/pom.xml b/nms/1_21_1/pom.xml new file mode 100644 index 000000000..8c360dde4 --- /dev/null +++ b/nms/1_21_1/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_1 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.1-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.1-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.1-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.21.1-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/AnvilInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/AnvilInventoryImpl.java new file mode 100644 index 000000000..9dee4d40c --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.18 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, net.minecraft.world.entity.player.Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull net.minecraft.world.entity.player.Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull net.minecraft.world.entity.player.Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/BeaconInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/BeaconInventoryImpl.java new file mode 100644 index 000000000..0b5a92b30 --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.18 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/CartographyTableInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..12fd766e1 --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.18 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/EnchantingTableInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..ccf3c367c --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/GrindstoneInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..143e87373 --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.18 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/MerchantInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/MerchantInventoryImpl.java new file mode 100644 index 000000000..5792119fb --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.18 + */ + @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.18 + */ + @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.18 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/SmithingTableInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..d16b1dada --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.1. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.18 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.18 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/StonecutterInventoryImpl.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/StonecutterInventoryImpl.java new file mode 100644 index 000000000..fb07889e6 --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_1.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R1.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/CustomInventoryUtil.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/CustomInventoryUtil.java new file mode 100644 index 000000000..e1d0af6d9 --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R1.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.18 + */ +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.18 + */ + @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_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/TextHolderUtil.java b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/TextHolderUtil.java new file mode 100644 index 000000000..16d0416fd --- /dev/null +++ b/nms/1_21_1/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_1/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_1.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.18 + */ +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.18 + */ + @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.18 + */ + @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.18 + */ + @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/nms/1_21_11/pom.xml b/nms/1_21_11/pom.xml new file mode 100644 index 000000000..5bdf031da --- /dev/null +++ b/nms/1_21_11/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_11 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.11-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.11-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.21.11-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/AnvilInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/AnvilInventoryImpl.java new file mode 100644 index 000000000..1ad863f45 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R7.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.11.6 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.6 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!super.remoteSlots.get(index).matches(super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, + @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/BeaconInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/BeaconInventoryImpl.java new file mode 100644 index 000000000..ca1dd5545 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R7.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon. + * + * @since 0.11.6 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.6 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/CartographyTableInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..ec39bff17 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/CartographyTableInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table. + * + * @since 0.11.6 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.6 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/EnchantingTableInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..19e25f7d6 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/EnchantingTableInventoryImpl.java @@ -0,0 +1,175 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R7.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table. + * + * @since 0.11.6 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.6 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/GrindstoneInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..a9f0fb3ea --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone. + * + * @since 0.11.6 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.6 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/MerchantInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/MerchantInventoryImpl.java new file mode 100644 index 000000000..d9fbaddd1 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentExactPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R7.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull org.bukkit.entity.Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.11.6 + */ + @NotNull + @Contract(value = "_ -> new", pure = true) + private ItemCost convertItemStackToItemCost(@NotNull net.minecraft.world.item.ItemStack itemStack) { + DataComponentExactPredicate predicate = DataComponentExactPredicate.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.11.6 + */ + @NotNull + @Contract(pure = true) + private ServerPlayer getServerPlayer(@NotNull org.bukkit.entity.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.11.6 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.6 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.6 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.6 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/SmithingTableInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..d28c4254d --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.11. This is only available for Minecraft 1.20 and higher. + * + * @since 0.11.6 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.11.6 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.6 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/StonecutterInventoryImpl.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/StonecutterInventoryImpl.java new file mode 100644 index 000000000..6f3dd2957 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/StonecutterInventoryImpl.java @@ -0,0 +1,203 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_11.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.StonecutterMenu; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R7.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.11 + * + * @since 0.11.6 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.6 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.6 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container stonecutter + * + * @since 0.11.6 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.6 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.6 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/CustomInventoryUtil.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/CustomInventoryUtil.java new file mode 100644 index 000000000..251e2ad01 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R7.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.11.6 + */ +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.11.6 + */ + @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_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/TextHolderUtil.java b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/TextHolderUtil.java new file mode 100644 index 000000000..20e2d3199 --- /dev/null +++ b/nms/1_21_11/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_11/util/TextHolderUtil.java @@ -0,0 +1,76 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_11.util; + +import com.github.stefvanschie.inventoryframework.adventuresupport.ComponentHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.RegistryOps; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.11.6 + */ +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.11.6 + */ + @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.11.6 + */ + @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.11.6 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + Codec codec = ComponentSerialization.CODEC; + HolderLookup.Provider provider = HolderLookup.Provider.create(Stream.empty()); + RegistryOps serializationContext = provider.createSerializationContext(JsonOps.INSTANCE); + + return codec.parse(serializationContext, holder.asJson()).getOrThrow(JsonParseException::new); + } +} diff --git a/nms/1_21_2-3/pom.xml b/nms/1_21_2-3/pom.xml new file mode 100644 index 000000000..43f6977dc --- /dev/null +++ b/nms/1_21_2-3/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_2-3 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.3-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.3-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.3-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.21.3-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.3-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/AnvilInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/AnvilInventoryImpl.java new file mode 100644 index 000000000..08ddb95e4 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/AnvilInventoryImpl.java @@ -0,0 +1,228 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R2.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.18 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/BeaconInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/BeaconInventoryImpl.java new file mode 100644 index 000000000..f5b4b6410 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R2.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.18 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final net.minecraft.world.entity.player.Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/CartographyTableInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..2297bbdaf --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.18 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/EnchantingTableInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..fe133e952 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R2.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/GrindstoneInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..1b89bc243 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.1 + * + * @since 0.10.18 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.18 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/MerchantInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/MerchantInventoryImpl.java new file mode 100644 index 000000000..b80f42473 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R2.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.18 + */ + @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.18 + */ + @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.18 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/SmithingTableInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..bcee3c4f6 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.2. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.18 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.18 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/StonecutterInventoryImpl.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/StonecutterInventoryImpl.java new file mode 100644 index 000000000..2aac07f12 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R2.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.2 + * + * @since 0.10.18 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.18 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/CustomInventoryUtil.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/CustomInventoryUtil.java new file mode 100644 index 000000000..eb2257b95 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R2.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.18 + */ +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.18 + */ + @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_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/TextHolderUtil.java b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/TextHolderUtil.java new file mode 100644 index 000000000..4f8075836 --- /dev/null +++ b/nms/1_21_2-3/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_2_3/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_2_3.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.18 + */ +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.18 + */ + @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.18 + */ + @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.18 + */ + @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/nms/1_21_4/pom.xml b/nms/1_21_4/pom.xml new file mode 100644 index 000000000..711ddcc78 --- /dev/null +++ b/nms/1_21_4/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_4 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.4-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.4-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.4-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.21.4-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.4-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/AnvilInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/AnvilInventoryImpl.java new file mode 100644 index 000000000..6e5fb8e0b --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R3.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.10.19 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!ItemStack.matches(super.remoteSlots.get(index), super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, + @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/BeaconInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/BeaconInventoryImpl.java new file mode 100644 index 000000000..42f621f8d --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R3.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.3 + * + * @since 0.10.19 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon + * + * @since 0.10.19 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final net.minecraft.world.entity.player.Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull net.minecraft.world.entity.player.Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/CartographyTableInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..a44fd8659 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/CartographyTableInventoryImpl.java @@ -0,0 +1,196 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table + * + * @since 0.10.19 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/EnchantingTableInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..942976d23 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/EnchantingTableInventoryImpl.java @@ -0,0 +1,180 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R3.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal enchanting table inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.19 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/GrindstoneInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..a7e3f0457 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone + * + * @since 0.10.19 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/MerchantInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/MerchantInventoryImpl.java new file mode 100644 index 000000000..357b24f81 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R3.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull net.minecraft.world.entity.player.Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.19 + */ + @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.19 + */ + @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.1 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable net.minecraft.world.entity.player.Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable net.minecraft.world.entity.player.Player player) {} + + @Override + protected void clearContainer(@Nullable net.minecraft.world.entity.player.Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/SmithingTableInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..255cdbcec --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.4. This is only available for Minecraft 1.20 and higher. + * + * @since 0.10.19 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.10.19 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/StonecutterInventoryImpl.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/StonecutterInventoryImpl.java new file mode 100644 index 000000000..a3e5e7d52 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_4.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R3.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.4 + * + * @since 0.10.19 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container enchanting table + * + * @since 0.10.19 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull net.minecraft.world.entity.player.Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable net.minecraft.world.entity.player.Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/CustomInventoryUtil.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/CustomInventoryUtil.java new file mode 100644 index 000000000..c10af101e --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R3.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.19 + */ +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.19 + */ + @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_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/TextHolderUtil.java b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/TextHolderUtil.java new file mode 100644 index 000000000..2d0053ff5 --- /dev/null +++ b/nms/1_21_4/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_4/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_4.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.19 + */ +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.19 + */ + @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.19 + */ + @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.19 + */ + @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/nms/1_21_5/pom.xml b/nms/1_21_5/pom.xml new file mode 100644 index 000000000..e45428c98 --- /dev/null +++ b/nms/1_21_5/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_5 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.5-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.5-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.5-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.21.5-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.5-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/AnvilInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/AnvilInventoryImpl.java new file mode 100644 index 000000000..d61db1c20 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/AnvilInventoryImpl.java @@ -0,0 +1,229 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R4.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.5 + * + * @since 0.11.0 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.11.0 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!super.remoteSlots.get(index).matches(super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, + @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/BeaconInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/BeaconInventoryImpl.java new file mode 100644 index 000000000..2376e0b2f --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R4.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.5 + * + * @since 0.11.0 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon. + * + * @since 0.11.0 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.0 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/CartographyTableInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..59a06d205 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/CartographyTableInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.5 + * + * @since 0.11.0 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table. + * + * @since 0.11.0 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/EnchantingTableInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..73106edd5 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/EnchantingTableInventoryImpl.java @@ -0,0 +1,175 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R4.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table. + * + * @since 0.11.0 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.0 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/GrindstoneInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..c1ddaa717 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.5 + * + * @since 0.11.0 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone. + * + * @since 0.11.0 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/MerchantInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/MerchantInventoryImpl.java new file mode 100644 index 000000000..3778f914f --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentExactPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R4.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.5 + * + * @since 0.11.0 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull org.bukkit.entity.Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.11.0 + */ + @NotNull + @Contract(value = "_ -> new", pure = true) + private ItemCost convertItemStackToItemCost(@NotNull net.minecraft.world.item.ItemStack itemStack) { + DataComponentExactPredicate predicate = DataComponentExactPredicate.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.11.0 + */ + @NotNull + @Contract(pure = true) + private ServerPlayer getServerPlayer(@NotNull org.bukkit.entity.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.11.0 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.0 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.0 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.0 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/SmithingTableInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..ce9fe9eca --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.5. This is only available for Minecraft 1.20 and higher. + * + * @since 0.11.0 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.11.0 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/StonecutterInventoryImpl.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/StonecutterInventoryImpl.java new file mode 100644 index 000000000..7fef5b6c8 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_5.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R4.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.5 + * + * @since 0.10.19 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.0 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.0 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container stonecutter + * + * @since 0.11.0 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.0 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.0 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/CustomInventoryUtil.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/CustomInventoryUtil.java new file mode 100644 index 000000000..661427426 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_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.11.0 + */ +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.11.0 + */ + @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_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/TextHolderUtil.java b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/TextHolderUtil.java new file mode 100644 index 000000000..b915c20f8 --- /dev/null +++ b/nms/1_21_5/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_5/util/TextHolderUtil.java @@ -0,0 +1,67 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_5.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.11.0 + */ +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.11.0 + */ + @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.11.0 + */ + @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.11.0 + */ + @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/nms/1_21_6-8/pom.xml b/nms/1_21_6-8/pom.xml new file mode 100644 index 000000000..b64c58c6c --- /dev/null +++ b/nms/1_21_6-8/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_6-8 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.8-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.8-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.8-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.21.8-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.8-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/AnvilInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/AnvilInventoryImpl.java new file mode 100644 index 000000000..5896d49c5 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/AnvilInventoryImpl.java @@ -0,0 +1,225 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R5.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.11.1 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.1 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!super.remoteSlots.get(index).matches(super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, + @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/BeaconInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/BeaconInventoryImpl.java new file mode 100644 index 000000000..489deefed --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/BeaconInventoryImpl.java @@ -0,0 +1,157 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R5.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon. + * + * @since 0.11.1 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.1 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/CartographyTableInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..524fcd325 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/CartographyTableInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table. + * + * @since 0.11.1 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.1 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/EnchantingTableInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..e24df53b3 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/EnchantingTableInventoryImpl.java @@ -0,0 +1,175 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R5.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table. + * + * @since 0.11.1 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.1 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/GrindstoneInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..c0a577ef2 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone. + * + * @since 0.11.1 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.1 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/MerchantInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/MerchantInventoryImpl.java new file mode 100644 index 000000000..c603aa96c --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentExactPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R5.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull org.bukkit.entity.Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.11.1 + */ + @NotNull + @Contract(value = "_ -> new", pure = true) + private ItemCost convertItemStackToItemCost(@NotNull net.minecraft.world.item.ItemStack itemStack) { + DataComponentExactPredicate predicate = DataComponentExactPredicate.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.11.1 + */ + @NotNull + @Contract(pure = true) + private ServerPlayer getServerPlayer(@NotNull org.bukkit.entity.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.11.1 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.1 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.1 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.1 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/SmithingTableInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..9865e2948 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/SmithingTableInventoryImpl.java @@ -0,0 +1,213 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.6 - 1.21.8. This is only available for Minecraft 1.20 and higher. + * + * @since 0.11.1 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.11.1 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.1 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/StonecutterInventoryImpl.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/StonecutterInventoryImpl.java new file mode 100644 index 000000000..7fa48b04f --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/StonecutterInventoryImpl.java @@ -0,0 +1,200 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R5.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.6 - 1.21.8 + * + * @since 0.11.1 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.1 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.1 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container stonecutter + * + * @since 0.11.1 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.1 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.1 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/CustomInventoryUtil.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/CustomInventoryUtil.java new file mode 100644 index 000000000..76aa652fa --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R5.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.11.1 + */ +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.11.1 + */ + @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_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/TextHolderUtil.java b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/TextHolderUtil.java new file mode 100644 index 000000000..440120823 --- /dev/null +++ b/nms/1_21_6-8/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_6_8/util/TextHolderUtil.java @@ -0,0 +1,76 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_6_8.util; + +import com.github.stefvanschie.inventoryframework.adventuresupport.ComponentHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.RegistryOps; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.11.1 + */ +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.11.1 + */ + @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.11.1 + */ + @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.11.1 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + Codec codec = ComponentSerialization.CODEC; + HolderLookup.Provider provider = HolderLookup.Provider.create(Stream.empty()); + RegistryOps serializationContext = provider.createSerializationContext(JsonOps.INSTANCE); + + return codec.parse(serializationContext, holder.asJson()).getOrThrow(JsonParseException::new); + } +} diff --git a/nms/1_21_9-10/pom.xml b/nms/1_21_9-10/pom.xml new file mode 100644 index 000000000..65fcea98e --- /dev/null +++ b/nms/1_21_9-10/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.github.stefvanschie.inventoryframework + IF-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + 1_21_9-10 + + + true + + + + + com.github.stefvanschie.inventoryframework + abstraction + ${project.version} + compile + + + org.spigotmc + spigot + 1.21.10-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.21.10-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.10-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.21.10-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.10-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/AnvilInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/AnvilInventoryImpl.java new file mode 100644 index 000000000..218b505ba --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/AnvilInventoryImpl.java @@ -0,0 +1,225 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.AnvilInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryAnvil; +import org.bukkit.craftbukkit.v1_21_R6.inventory.view.CraftAnvilView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal anvil inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class AnvilInventoryImpl extends AnvilInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer inputSlots = new SimpleContainer(2); + SimpleContainer resultSlot = new SimpleContainer(1); + + return new CraftInventoryAnvil(null, inputSlots, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ANVIL; + } + + @Override + public Container getInventory() { + return new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerAnvilImpl(containerId, player, inputSlots, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider {} + + /** + * A custom container anvil for responding to item renaming + * + * @since 0.11.5 + */ + private class ContainerAnvilImpl extends AnvilMenu { + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftAnvilView bukkitEntity; + + /** + * Creates a new custom anvil container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.5 + */ + public ContainerAnvilImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + this.checkReachable = false; + this.cost.set(AnvilInventoryImpl.super.cost); + + CompoundContainer compoundContainer = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, compoundContainer); + updateSlot(1, compoundContainer); + updateSlot(2, compoundContainer); + } + + @Override + public CraftAnvilView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryAnvil inventory = new CraftInventoryAnvil( + this.access.getLocation(), + this.inputSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); + this.bukkitEntity.updateFromLegacy(inventory); + + return this.bukkitEntity; + } + + @Override + public void broadcastChanges() { + if (super.cost.checkAndClearUpdateFlag()) { + broadcastFullState(); + } else { + for (int index = 0; index < super.slots.size(); index++) { + if (!super.remoteSlots.get(index).matches(super.slots.get(index).getItem())) { + broadcastFullState(); + return; + } + } + } + } + + @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 slotsChanged(Container container) { + broadcastChanges(); + } + + @Override + public void clicked(int index, int dragData, ClickType clickType, Player player) { + super.clicked(index, dragData, clickType, player); + + //client predicts first slot, so send data to override + broadcastFullState(); + } + + @Override + public void createResult() {} + + @Override + public void removed(@NotNull Player nmsPlayer) {} + + @Override + protected void clearContainer(@NotNull Player player, + @NotNull Container inventory) {} + + @Override + protected void onTake(@NotNull Player player, @NotNull ItemStack stack) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/BeaconInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/BeaconInventoryImpl.java new file mode 100644 index 000000000..6849fc390 --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/BeaconInventoryImpl.java @@ -0,0 +1,161 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.BeaconInventory; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryBeacon; +import org.bukkit.craftbukkit.v1_21_R6.inventory.view.CraftBeaconView; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal beacon inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class BeaconInventoryImpl extends BeaconInventory { + + @NotNull + @Override + public Inventory createInventory() { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerBeaconImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return Component.literal("Beacon"); + } + }; + + container.setMaxStackSize(1); //client limitation + + return new CraftInventoryBeacon(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.BEACON; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with one slot. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container beacon. + * + * @since 0.11.5 + */ + private static class ContainerBeaconImpl extends BeaconMenu { + + /** + * The player viewing this menu. + */ + @NotNull + private final Player player; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftBeaconView bukkitEntity; + + /** + * Creates a new custom beacon container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @since 0.11.5 + */ + public ContainerBeaconImpl(int containerId, @NotNull Player player, @NotNull SimpleContainer inputSlot) { + super(containerId, player.getInventory(), new SimpleContainerData(3), + ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.player = player; + this.inputSlot = inputSlot; + + super.checkReachable = false; + + Slot slot = super.slots.get(0); + + Slot newSlot = new Slot(inputSlot, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(0, newSlot); + } + + @NotNull + @Override + public CraftBeaconView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryBeacon inventory = new CraftInventoryBeacon(this.inputSlot); + + this.bukkitEntity = new CraftBeaconView(this.player.getBukkitEntity(), inventory, this); + + return this.bukkitEntity; + } + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/CartographyTableInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/CartographyTableInventoryImpl.java new file mode 100644 index 000000000..05cf3a4a7 --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/CartographyTableInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.CartographyTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CartographyTableMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryCartography; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal cartography table inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class CartographyTableInventoryImpl extends CartographyTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerCartographyTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryCartography(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.CARTOGRAPHY; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container cartography table. + * + * @since 0.11.5 + */ + private static class ContainerCartographyTableImpl extends CartographyTableMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom cartography table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @param resultSlot the result slot + * @since 0.11.5 + */ + public ContainerCartographyTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryCartography inventory = new CraftInventoryCartography(this.inputSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/EnchantingTableInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/EnchantingTableInventoryImpl.java new file mode 100644 index 000000000..bf7b457fe --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/EnchantingTableInventoryImpl.java @@ -0,0 +1,175 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.EnchantingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryEnchanting; +import org.bukkit.craftbukkit.v1_21_R6.inventory.view.CraftEnchantmentView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class EnchantingTableInventoryImpl extends EnchantingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerEnchantingTableImpl(containerId, player, this); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryEnchanting(container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.ENCHANTING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container enchanting table. + * + * @since 0.11.5 + */ + private static class ContainerEnchantingTableImpl extends EnchantmentMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the input slots. + */ + @NotNull + private final SimpleContainer inputSlots; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftEnchantmentView bukkitEntity; + + /** + * Creates a new custom enchanting table container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param inputSlots the input slots + * @since 0.11.5 + */ + public ContainerEnchantingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlots + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlots = inputSlots; + + super.checkReachable = false; + + updateSlot(0, inputSlots); + updateSlot(1, inputSlots); + } + + @NotNull + @Override + public CraftEnchantmentView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.inputSlots); + + this.bukkitEntity = new CraftEnchantmentView(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/GrindstoneInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/GrindstoneInventoryImpl.java new file mode 100644 index 000000000..9d547cc81 --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/GrindstoneInventoryImpl.java @@ -0,0 +1,195 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.GrindstoneInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryGrindstone; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal grindstone inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class GrindstoneInventoryImpl extends GrindstoneInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerGrindstoneImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryGrindstone(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.GRINDSTONE; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(2); + } + } + + /** + * A custom container grindstone. + * + * @since 0.11.5 + */ + private static class ContainerGrindstoneImpl extends GrindstoneMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.5 + */ + public ContainerGrindstoneImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.itemsSlots, this.resultSlot); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/MerchantInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/MerchantInventoryImpl.java new file mode 100644 index 000000000..c8e0f94fe --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/MerchantInventoryImpl.java @@ -0,0 +1,321 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.MerchantInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.component.DataComponentExactPredicate; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.npc.ClientSideMerchant; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MerchantContainer; +import net.minecraft.world.inventory.MerchantMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.trading.ItemCost; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.item.trading.MerchantOffers; +import org.bukkit.craftbukkit.v1_21_R6.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryMerchant; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R6.inventory.view.CraftMerchantView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Internal merchant inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class MerchantInventoryImpl extends MerchantInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + Merchant merchant = new ClientSideMerchant(null); + + MerchantContainer container = new InventoryViewProvider(merchant) { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerMerchantImpl(containerId, player, this, merchant); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryMerchant(merchant, container) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.MERCHANT; + } + + @Override + public MerchantContainer getInventory() { + return container; + } + }; + } + + @Override + public void sendMerchantOffers(@NotNull org.bukkit.entity.Player player, + @NotNull List> trades, + int level, int experience) { + MerchantOffers offers = new MerchantOffers(); + + for (Map.Entry 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.11.5 + */ + @NotNull + @Contract(value = "_ -> new", pure = true) + private ItemCost convertItemStackToItemCost(@NotNull net.minecraft.world.item.ItemStack itemStack) { + DataComponentExactPredicate predicate = DataComponentExactPredicate.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.11.5 + */ + @NotNull + @Contract(pure = true) + private ServerPlayer getServerPlayer(@NotNull org.bukkit.entity.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.11.5 + */ + @Contract(pure = true) + private int getContainerId(@NotNull net.minecraft.world.entity.player.Player nmsPlayer) { + return nmsPlayer.containerMenu.containerId; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends MerchantContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with two slots. + * + * @param merchant the merchant + * @since 0.11.5 + */ + public InventoryViewProvider(@NotNull Merchant merchant) { + super(merchant); + } + } + + /** + * A custom container merchant + * + * @since 0.11.5 + */ + private static class ContainerMerchantImpl extends MerchantMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final MerchantContainer container; + + /** + * The merchant. + */ + @NotNull + private final Merchant merchant; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftMerchantView bukkitEntity; + + /** + * Creates a new custom grindstone container for the specified player. + * + * @param containerId the container id + * @param player the player + * @param container the items slots + * @param merchant the merchant + * @since 0.11.5 + */ + public ContainerMerchantImpl( + int containerId, + @NotNull Player player, + @NotNull MerchantContainer container, + @NotNull Merchant merchant + ) { + super(containerId, player.getInventory(), merchant); + + this.humanEntity = player.getBukkitEntity(); + this.container = container; + this.merchant = merchant; + + super.checkReachable = false; + + updateSlot(0, container); + updateSlot(1, container); + + Slot slot = super.slots.get(2); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y) { + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPickup(@Nullable Player player) { + return false; + } + + @Contract(value = "_ -> false", pure = true) + @Override + public boolean mayPlace(@Nullable net.minecraft.world.item.ItemStack itemStack) { + return false; + } + }; + newSlot.index = slot.index; + + super.slots.set(2, newSlot); + } + + @NotNull + @Override + public CraftMerchantView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryMerchant inventory = new CraftInventoryMerchant(this.merchant, this.container); + + this.bukkitEntity = new CraftMerchantView(this.humanEntity, inventory, this, this.merchant); + + return this.bukkitEntity; + } + + @Override + public void slotsChanged(@Nullable Container container) {} + + @Override + public void removed(@Nullable Player player) {} + + @Override + protected void clearContainer(@Nullable Player player, @Nullable Container container) {} + + @Override + public void setSelectionHint(int i) {} + + @Override + public void tryMoveItems(int i) {} + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/SmithingTableInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/SmithingTableInventoryImpl.java new file mode 100644 index 000000000..2c1b90cd1 --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/SmithingTableInventoryImpl.java @@ -0,0 +1,217 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.SmithingTableInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.SmithingMenu; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventorySmithing; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal smithing table inventory for 1.21.9 - 1.21.10. This is only available for Minecraft 1.20 and higher. + * + * @since 0.11.5 + */ +public class SmithingTableInventoryImpl extends SmithingTableInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + ResultContainer resultSlot = new ResultContainer(); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerSmithingTableImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventorySmithing(null, container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.SMITHING; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(3); + } + } + + /** + * A custom container smithing table + * + * @since 0.11.5 + */ + private static class ContainerSmithingTableImpl extends SmithingMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer itemsSlots; + + /** + * The container for the result slot. + */ + @NotNull + private final ResultContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftInventoryView bukkitEntity; + + /** + * Creates a new custom smithing table container for the specified player + * + * @param containerId the container id + * @param player the player + * @param itemsSlots the items slots + * @param resultSlot the result slot + * @since 0.11.5 + */ + public ContainerSmithingTableImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer itemsSlots, + @NotNull ResultContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.itemsSlots = itemsSlots; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(itemsSlots, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + updateSlot(2, container); + updateSlot(3, container); + } + + @NotNull + @Override + public CraftInventoryView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventory inventory = new CraftInventorySmithing( + this.access.getLocation(), + this.itemsSlots, + this.resultSlot + ); + + this.bukkitEntity = new CraftInventoryView<>(this.humanEntity, inventory, this); + + return this.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) {} + + @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; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/StonecutterInventoryImpl.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/StonecutterInventoryImpl.java new file mode 100644 index 000000000..78a97241d --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/StonecutterInventoryImpl.java @@ -0,0 +1,203 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10; + +import com.github.stefvanschie.inventoryframework.abstraction.StonecutterInventory; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util.TextHolderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.CompoundContainer; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.StonecutterMenu; +import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftInventoryStonecutter; +import org.bukkit.craftbukkit.v1_21_R6.inventory.view.CraftStonecutterView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Internal stonecutter inventory for 1.21.9 - 1.21.10 + * + * @since 0.11.5 + */ +public class StonecutterInventoryImpl extends StonecutterInventory { + + @NotNull + @Contract(pure = true) + @Override + public Inventory createInventory(@NotNull TextHolder title) { + SimpleContainer resultSlot = new SimpleContainer(1); + + Container container = new InventoryViewProvider() { + @NotNull + @Contract(pure = true) + @Override + public AbstractContainerMenu createMenu( + int containerId, + @Nullable net.minecraft.world.entity.player.Inventory inventory, + @NotNull Player player + ) { + return new ContainerStonecutterImpl(containerId, player, this, resultSlot); + } + + @NotNull + @Contract(pure = true) + @Override + public Component getDisplayName() { + return TextHolderUtil.toComponent(title); + } + }; + + return new CraftInventoryStonecutter(container, resultSlot) { + @NotNull + @Contract(pure = true) + @Override + public InventoryType getType() { + return InventoryType.STONECUTTER; + } + + @Override + public Container getInventory() { + return container; + } + }; + } + + /** + * This is a nice hack to get CraftBukkit to create custom inventories. By providing a container that is also a menu + * provider, CraftBukkit will allow us to create a custom menu, rather than picking one of the built-in options. + * That way, we can provide a menu with custom behaviour. + * + * @since 0.11.5 + */ + private abstract static class InventoryViewProvider extends SimpleContainer implements MenuProvider { + + /** + * Creates a new inventory view provider with three slots. + * + * @since 0.11.5 + */ + public InventoryViewProvider() { + super(1); + } + } + + /** + * A custom container stonecutter + * + * @since 0.11.5 + */ + private static class ContainerStonecutterImpl extends StonecutterMenu { + + /** + * The human entity viewing this menu. + */ + @NotNull + private final HumanEntity humanEntity; + + /** + * The container for the items slots. + */ + @NotNull + private final SimpleContainer inputSlot; + + /** + * The container for the result slot. + */ + @NotNull + private final SimpleContainer resultSlot; + + /** + * The corresponding Bukkit view. Will be not null after the first call to {@link #getBukkitView()} and null + * prior. + */ + @Nullable + private CraftStonecutterView bukkitEntity; + + /** + * Creates a new custom stonecutter container for the specified player + * + * @param containerId the container id + * @param player the player + * @param inputSlot the input slot + * @param resultSlot the result slot + * @since 0.11.5 + */ + public ContainerStonecutterImpl( + int containerId, + @NotNull Player player, + @NotNull SimpleContainer inputSlot, + @NotNull SimpleContainer resultSlot + ) { + super(containerId, player.getInventory(), ContainerLevelAccess.create(player.level(), BlockPos.ZERO)); + + this.humanEntity = player.getBukkitEntity(); + this.inputSlot = inputSlot; + this.resultSlot = resultSlot; + + super.checkReachable = false; + + CompoundContainer container = new CompoundContainer(inputSlot, resultSlot); + + updateSlot(0, container); + updateSlot(1, container); + } + + @NotNull + @Override + public CraftStonecutterView getBukkitView() { + if (this.bukkitEntity != null) { + return this.bukkitEntity; + } + + CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.inputSlot, this.resultSlot); + + this.bukkitEntity = new CraftStonecutterView(this.humanEntity, inventory, this); + + return this.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) {} + + @Contract(value = "_, _ -> false", pure = true) + @Override + public boolean clickMenuButton(@Nullable Player player, int index) { + return false; + } + + /** + * Updates the current slot at the specified index to a new slot. The new slot will have the same slot, x, y, + * and index as the original. The container of the new slot will be set to the value specified. + * + * @param slotIndex the slot index to update + * @param container the container of the new slot + * @since 0.11.5 + */ + private void updateSlot(int slotIndex, @NotNull Container container) { + Slot slot = super.slots.get(slotIndex); + + Slot newSlot = new Slot(container, slot.slot, slot.x, slot.y); + newSlot.index = slot.index; + + super.slots.set(slotIndex, newSlot); + } + } +} diff --git a/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/CustomInventoryUtil.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/CustomInventoryUtil.java new file mode 100644 index 000000000..d6229f970 --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/CustomInventoryUtil.java @@ -0,0 +1,41 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_21_R6.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.11.5 + */ +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.11.5 + */ + @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_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/TextHolderUtil.java b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/TextHolderUtil.java new file mode 100644 index 000000000..e3f8998ee --- /dev/null +++ b/nms/1_21_9-10/src/main/java/com/github/stefvanschie/inventoryframework/nms/v1_21_9_10/util/TextHolderUtil.java @@ -0,0 +1,76 @@ +package com.github.stefvanschie.inventoryframework.nms.v1_21_9_10.util; + +import com.github.stefvanschie.inventoryframework.adventuresupport.ComponentHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.StringHolder; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.RegistryOps; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +/** + * A utility class for adding {@link TextHolder} support. + * + * @since 0.11.5 + */ +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.11.5 + */ + @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.11.5 + */ + @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.11.5 + */ + @NotNull + @Contract(pure = true) + private static Component toComponent(@NotNull ComponentHolder holder) { + Codec codec = ComponentSerialization.CODEC; + HolderLookup.Provider provider = HolderLookup.Provider.create(Stream.empty()); + RegistryOps serializationContext = provider.createSerializationContext(JsonOps.INSTANCE); + + return codec.parse(serializationContext, holder.asJson()).getOrThrow(JsonParseException::new); + } +} diff --git a/nms/abstraction/pom.xml b/nms/abstraction/pom.xml index e0566382b..247208ff2 100644 --- a/nms/abstraction/pom.xml +++ b/nms/abstraction/pom.xml @@ -5,7 +5,7 @@ IF-parent com.github.stefvanschie.inventoryframework - 0.9.8 + 0.12.0-SNAPSHOT ../../pom.xml 4.0.0 @@ -24,6 +24,12 @@ + + com.github.stefvanschie.inventoryframework + adventure-support + ${project.version} + compile + org.spigotmc spigot-api diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/AnvilInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/AnvilInventory.java index e70fdf48e..9ef615cc1 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/AnvilInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/AnvilInventory.java @@ -1,11 +1,12 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.abstraction.util.ObservableValue; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; /** * An anvil inventory @@ -15,97 +16,42 @@ public abstract class AnvilInventory { /** - * The inventory holder + * The name input text. */ @NotNull - protected InventoryHolder inventoryHolder; + protected final ObservableValue<@NotNull String> observableText = new ObservableValue<>(""); /** - * The rename text + * The enchantment cost displayed */ - @NotNull - protected String text = ""; + protected short cost; /** - * Creates a new anvil inventory for the specified inventory holder + * Sets the enchantment level cost for this anvil gui. Taking the item from the result slot will not actually remove + * these levels. Having a cost specified does not impede a player's ability to take the item in the result item, + * even if the player does not have the specified amount of levels. The cost must be a non-negative number. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 + * @param cost the cost + * @since 0.10.8 + * @throws IllegalArgumentException when the cost is less than zero */ - public AnvilInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; + public void setCost(short cost) { + if (cost < 0){ + throw new IllegalArgumentException("Cost must be non-negative"); + } + + this.cost = cost; } /** - * Opens the inventory for the specified player + * Creates an anvil inventory. * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the items to show - * @since 0.8.0 - */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the anvil - * @param items the items to send - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Sends the result item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendResultItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * Sends the first item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendFirstItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * Sends the second item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendSecondItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * 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.8.0 - */ - public abstract void setCursor(@NotNull Player player, @NotNull ItemStack item); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); - - /** - * Clears the result item for the specified player - * - * @param player the player to clear the result item of - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void clearResultItem(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); /** * Gets the text shown in the rename slot of the anvil @@ -116,6 +62,22 @@ public AnvilInventory(@NotNull InventoryHolder inventoryHolder) { @NotNull @Contract(pure = true) public String getRenameText() { + String text = observableText.get(); + + if (text == null) { + throw new IllegalStateException("Rename text is null"); + } + return text; } + + /** + * Subscribes to changes of the name input. + * + * @param onNameInputChanged the consumer to call when the name input changes + * @since 0.10.10 + */ + public void subscribeToNameInputChanges(@NotNull Consumer onNameInputChanged) { + this.observableText.subscribe(onNameInputChanged); + } } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/BeaconInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/BeaconInventory.java index ec708b27a..3197ed4d0 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/BeaconInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/BeaconInventory.java @@ -1,10 +1,7 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A beacon inventory @@ -14,44 +11,11 @@ public abstract class BeaconInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new beacon inventory for the specified inventory holder + * Creates a beacon inventory. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public BeaconInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player - * - * @param player the player to open the inventory for - * @param item the item to send - * @since 0.8.0 - */ - public abstract void openInventory(@NotNull Player player, @Nullable ItemStack item); - - /** - * Sends the top item to the inventory for the specified player. - * - * @param player the player for which to open the beacon - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/CartographyTableInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/CartographyTableInventory.java index da4f1db58..09e1e61b5 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/CartographyTableInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/CartographyTableInventory.java @@ -1,10 +1,8 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A cartography table inventory @@ -14,45 +12,12 @@ public abstract class CartographyTableInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new cartography table inventory for the specified inventory holder + * Creates a cartography table inventory. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 - */ - public CartographyTableInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player - * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the top items of the inventory - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the cartography table - * @param items the top items of the inventory - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/EnchantingTableInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/EnchantingTableInventory.java index dc8b97bf0..53b7dc2d5 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/EnchantingTableInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/EnchantingTableInventory.java @@ -1,10 +1,8 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * An enchanting table inventory @@ -14,45 +12,12 @@ public abstract class EnchantingTableInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new enchanting table inventory for the specified inventory holder + * Creates a cartography table inventory. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 - */ - public EnchantingTableInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player - * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the top items - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the enchanting table - * @param items the items to send - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/GrindstoneInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/GrindstoneInventory.java index 314eb1996..bf8a7b66f 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/GrindstoneInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/GrindstoneInventory.java @@ -1,10 +1,8 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A grindstone inventory @@ -14,45 +12,12 @@ public abstract class GrindstoneInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new grindstone inventory for the specified inventory holder + * Creates a grindstone inventory. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 - */ - public GrindstoneInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player - * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the top items - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the grindstone - * @param items the items to send - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/MerchantInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/MerchantInventory.java new file mode 100644 index 000000000..92dfb705b --- /dev/null +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/MerchantInventory.java @@ -0,0 +1,44 @@ +package com.github.stefvanschie.inventoryframework.abstraction; + +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +/** + * A merchant inventory + * + * @since 0.10.1 + */ +public abstract class MerchantInventory { + + /** + * Creates a merchant inventory. + * + * @param title the title of the inventory + * @return the inventory + * @since 0.11.0 + */ + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); + + /** + * Sends the merchant offers to the player, combined with the merchants level and experience. + * + * @param player the player to send this to + * @param trades the trades to send + * @param level the level of the merchant + * @param experience the experience of the merchant + * @since 0.10.1 + */ + public abstract void sendMerchantOffers( + @NotNull Player player, + @NotNull List> trades, + int level, + int experience + ); +} diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/SmithingTableInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/SmithingTableInventory.java index e34f65ea0..c2f19c43a 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/SmithingTableInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/SmithingTableInventory.java @@ -1,10 +1,8 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A smithing table inventory @@ -14,89 +12,12 @@ public abstract class SmithingTableInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new smithing table inventory for the specified inventory holder - * - * @param inventoryHolder the inventory holder - * @since 0.8.0 - */ - public SmithingTableInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player + * Creates a smithing table inventory. * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the top items - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the smithing table - * @param items the items to send - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Sends the result item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendResultItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * Sends the first item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendFirstItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * Sends the second item to the specified player - * - * @param player the player to send the item to - * @param item the item to send - * @since 0.8.0 - */ - public abstract void sendSecondItem(@NotNull Player player, @Nullable ItemStack item); - - /** - * 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.8.0 - */ - public abstract void setCursor(@NotNull Player player, @NotNull ItemStack item); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); - - /** - * Clears the result item for the specified player - * - * @param player the player to clear the result item of - * @since 0.8.0 - */ - public abstract void clearResultItem(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/StonecutterInventory.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/StonecutterInventory.java index 7481979d8..d4acd331e 100644 --- a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/StonecutterInventory.java +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/StonecutterInventory.java @@ -1,10 +1,8 @@ package com.github.stefvanschie.inventoryframework.abstraction; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; +import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A stonecutter inventory @@ -14,45 +12,12 @@ public abstract class StonecutterInventory { /** - * The inventory holder - */ - @NotNull - protected InventoryHolder inventoryHolder; - - /** - * Creates a new stonecutter inventory for the specified stonecutter gui + * Creates a stonecutter inventory. * - * @param inventoryHolder the inventory holder - * @since 0.8.0 - */ - public StonecutterInventory(@NotNull InventoryHolder inventoryHolder) { - this.inventoryHolder = inventoryHolder; - } - - /** - * Opens the inventory for the specified player - * - * @param player the player to open the inventory for * @param title the title of the inventory - * @param items the top items - * @since 0.8.0 + * @return the inventory + * @since 0.11.0 */ - public abstract void openInventory(@NotNull Player player, @NotNull String title, @Nullable ItemStack[] items); - - /** - * Sends the top items to the inventory for the specified player. - * - * @param player the player for which to open the stonecutter - * @param items the items to send - * @since 0.8.0 - */ - public abstract void sendItems(@NotNull Player player, @Nullable ItemStack[] items); - - /** - * Clears the cursor of the specified player - * - * @param player the player to clear the cursor of - * @since 0.8.0 - */ - public abstract void clearCursor(@NotNull Player player); + @NotNull + public abstract Inventory createInventory(@NotNull TextHolder title); } diff --git a/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/util/ObservableValue.java b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/util/ObservableValue.java new file mode 100644 index 000000000..c0d5309f9 --- /dev/null +++ b/nms/abstraction/src/main/java/com/github/stefvanschie/inventoryframework/abstraction/util/ObservableValue.java @@ -0,0 +1,80 @@ +package com.github.stefvanschie.inventoryframework.abstraction.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.function.Consumer; + +/** + * A value whose modifications can be observed. + * + * @param the type of value to store + * @since 0.10.10 + */ +public class ObservableValue { + + /** + * A collection of subscribers that should be notified on updates. + */ + @NotNull + private final Collection> subscribers = new HashSet<>(); + + /** + * The current value + */ + @Nullable + private T value; + + /** + * Creates a new observable value with the given default value. + * + * @param defaultValue the default value + * @since 0.10.10 + */ + public ObservableValue(@Nullable T defaultValue) { + this.value = defaultValue; + } + + /** + * Updates the old value to the given new value. This will notify all the subscribers before updating the new value. + * Subscribers may observe the old value by using {@link #get()}. This will always notify the subscribers, even if + * the new value is the same as the old value. + * + * @param newValue the new value + * @since 0.10.10 + */ + public void set(T newValue) { + for (Consumer subscriber : this.subscribers) { + subscriber.accept(newValue); + } + + this.value = newValue; + } + + /** + * Subscribes to modifications of this value. The provided consumer will be called every time this value changes. + * + * @param consumer the consumer to call upon updates of this value + * @since 0.10.10 + */ + public void subscribe(@NotNull Consumer consumer) { + this.subscribers.add(consumer); + } + + /** + * Gets the current value of this item. If this is called from within a subscriber, then this is the value from + * before the current in-progress update. + * + * @return the current value + * @since 0.10.10 + */ + @Nullable + @Contract(pure = true) + public T get() { + return this.value; + } + +} diff --git a/pom.xml b/pom.xml index 37b63b40e..9285339c4 100644 --- a/pom.xml +++ b/pom.xml @@ -7,11 +7,28 @@ IF nms/abstraction - nms/1_16_R3 - nms/1_16_R2 - nms/1_16_R1 - nms/1_15_R1 - nms/1_14_R1 + nms/1_21_11 + nms/1_21_9-10 + nms/1_21_6-8 + nms/1_21_5 + nms/1_21_4 + nms/1_21_2-3 + nms/1_21_1 + nms/1_21_0 + nms/1_20_6 + nms/1_20_5 + nms/1_20_3-4 + nms/1_20_2 + nms/1_20_1 + nms/1_20_0 + nms/1_19_4 + nms/1_18_2 + nms/1_17_1 + nms/1_16_5 + adventure-support + inventory-view/iv-abstract-class + inventory-view/iv-abstraction + inventory-view/iv-interface @@ -19,11 +36,12 @@ 1.8 true UTF-8 + 4.25.0 com.github.stefvanschie.inventoryframework IF-parent - 0.9.8 + 0.12.0-SNAPSHOT pom IF @@ -53,20 +71,16 @@ org.jetbrains annotations - 19.0.0 + 26.0.2-1 provided - ossrh - https://oss.sonatype.org/content/repositories/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - @@ -77,7 +91,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.8 sign-artifacts @@ -98,35 +112,35 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.0 + 3.5.4 - org.junit.platform - junit-platform-surefire-provider - 1.2.0-M1 + org.apache.maven.surefire + surefire-junit-platform + 3.5.4 org.junit.jupiter junit-jupiter-engine - 5.2.0 + 6.0.1 - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published org.apache.maven.plugins maven-source-plugin - 3.2.0 + 3.4.0 attach-sources @@ -139,7 +153,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.12.0 attach-javadocs @@ -148,6 +162,12 @@ + + + https://hub.spigotmc.org/javadocs/bukkit/ + https://jd.adventure.kyori.net/api/4.10.0/ + +