From c94922514a44d7b15d5fd000b4b82acd259ba751 Mon Sep 17 00:00:00 2001 From: Miles <81843550+Y2Kwastaken@users.noreply.github.com> Date: Sat, 11 Jan 2025 19:21:24 +0000 Subject: [PATCH] MenuType API addition InventoryView Builders (#11816) --- build-data/paper.at | 1 + .../src/main/java/org/bukkit/Bukkit.java | 17 ++- .../src/main/java/org/bukkit/Server.java | 27 +++-- .../java/org/bukkit/entity/HumanEntity.java | 33 +++++- .../java/org/bukkit/inventory/MenuType.java | 64 +++++----- .../view/builder/InventoryViewBuilder.java | 38 ++++++ .../builder/LocationInventoryViewBuilder.java | 55 +++++++++ .../builder/MerchantInventoryViewBuilder.java | 44 +++++++ .../inventory/view/builder/package-info.java | 9 ++ .../AbstractContainerMenu.java.patch | 3 +- .../world/inventory/ChestMenu.java.patch | 13 ++- .../world/inventory/MerchantMenu.java.patch | 8 ++ .../world/inventory/ShulkerBoxMenu.java.patch | 13 ++- .../org/bukkit/craftbukkit/CraftServer.java | 6 + .../craftbukkit/entity/CraftHumanEntity.java | 8 ++ .../craftbukkit/event/CraftEventFactory.java | 1 + .../craftbukkit/inventory/CraftContainer.java | 2 +- .../craftbukkit/inventory/CraftMenuType.java | 31 +++-- .../inventory/util/CraftMenus.java | 110 +++++++++++++----- .../CraftAbstractInventoryViewBuilder.java | 48 ++++++++ ...tAbstractLocationInventoryViewBuilder.java | 48 ++++++++ ...aftAccessLocationInventoryViewBuilder.java | 45 +++++++ .../CraftBlockEntityInventoryViewBuilder.java | 74 ++++++++++++ .../CraftDoubleChestInventoryViewBuilder.java | 48 ++++++++ .../CraftMerchantInventoryViewBuilder.java | 78 +++++++++++++ .../CraftStandardInventoryViewBuilder.java | 26 +++++ .../inventory/view/builder/package-info.java | 4 + 27 files changed, 759 insertions(+), 95 deletions(-) create mode 100644 paper-api/src/main/java/org/bukkit/inventory/view/builder/InventoryViewBuilder.java create mode 100644 paper-api/src/main/java/org/bukkit/inventory/view/builder/LocationInventoryViewBuilder.java create mode 100644 paper-api/src/main/java/org/bukkit/inventory/view/builder/MerchantInventoryViewBuilder.java create mode 100644 paper-api/src/main/java/org/bukkit/inventory/view/builder/package-info.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractLocationInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAccessLocationInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftBlockEntityInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftDoubleChestInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftMerchantInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftStandardInventoryViewBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/package-info.java diff --git a/build-data/paper.at b/build-data/paper.at index 4945a796a6..26cdb6eafe 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -725,6 +725,7 @@ public-f net.minecraft.world.item.trading.MerchantOffer rewardExp public-f net.minecraft.world.item.trading.MerchantOffer xp public-f net.minecraft.world.level.LevelSettings hardcore public-f net.minecraft.world.level.LevelSettings levelName +public-f net.minecraft.world.level.block.ChestBlock MENU_PROVIDER_COMBINER public-f net.minecraft.world.level.block.entity.BannerBlockEntity baseColor public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner normalConfig public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner ominousConfig diff --git a/paper-api/src/main/java/org/bukkit/Bukkit.java b/paper-api/src/main/java/org/bukkit/Bukkit.java index 9eecd3a68a..9196b1e62b 100644 --- a/paper-api/src/main/java/org/bukkit/Bukkit.java +++ b/paper-api/src/main/java/org/bukkit/Bukkit.java @@ -40,6 +40,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemCraftResult; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MenuType; import org.bukkit.inventory.Merchant; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.ItemMeta; @@ -1925,7 +1926,10 @@ public final class Bukkit { * @param title the title of the corresponding merchant inventory, displayed * when the merchant inventory is viewed * @return a new merchant + * @deprecated The title parameter is no-longer needed when used with + * {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}. */ + @Deprecated(since = "1.21.4") public static @NotNull Merchant createMerchant(net.kyori.adventure.text.@Nullable Component title) { return server.createMerchant(title); } @@ -1936,7 +1940,8 @@ public final class Bukkit { * @param title the title of the corresponding merchant inventory, displayed * when the merchant inventory is viewed * @return a new merchant - * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)} + * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}. The title parameter is + * no-longer needed when used with {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()} */ @NotNull @Deprecated // Paper @@ -1944,6 +1949,16 @@ public final class Bukkit { return server.createMerchant(title); } + /** + * Creates an empty merchant. + * + * @return a new merchant + */ + @NotNull + public static Merchant createMerchant() { + return server.createMerchant(); + } + /** * Gets the amount of consecutive neighbor updates before skipping * additional ones. diff --git a/paper-api/src/main/java/org/bukkit/Server.java b/paper-api/src/main/java/org/bukkit/Server.java index 041ebeb283..11923ef0ea 100644 --- a/paper-api/src/main/java/org/bukkit/Server.java +++ b/paper-api/src/main/java/org/bukkit/Server.java @@ -43,6 +43,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemCraftResult; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MenuType; import org.bukkit.inventory.Merchant; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.ItemMeta; @@ -1565,11 +1566,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * <br> * {@link InventoryType#WORKBENCH} will not process crafting recipes if * created with this method. Use - * {@link Player#openWorkbench(Location, boolean)} instead. + * {@link MenuType#CRAFTING} instead. * <br> * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s * for possible enchanting results. Use - * {@link Player#openEnchanting(Location, boolean)} instead. + * {@link MenuType#ENCHANTMENT} instead. * * @param owner the holder of the inventory, or null to indicate no holder * @param type the type of inventory to create @@ -1592,11 +1593,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * <br> * {@link InventoryType#WORKBENCH} will not process crafting recipes if * created with this method. Use - * {@link Player#openWorkbench(Location, boolean)} instead. + * {@link MenuType#CRAFTING} instead. * <br> * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s * for possible enchanting results. Use - * {@link Player#openEnchanting(Location, boolean)} instead. + * {@link MenuType#ENCHANTMENT} instead. * * @param owner The holder of the inventory; can be null if there's no holder. * @param type The type of inventory to create. @@ -1620,11 +1621,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * <br> * {@link InventoryType#WORKBENCH} will not process crafting recipes if * created with this method. Use - * {@link Player#openWorkbench(Location, boolean)} instead. + * {@link MenuType#CRAFTING} instead. * <br> * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s * for possible enchanting results. Use - * {@link Player#openEnchanting(Location, boolean)} instead. + * {@link MenuType#ENCHANTMENT} instead. * * @param owner The holder of the inventory; can be null if there's no holder. * @param type The type of inventory to create. @@ -1691,7 +1692,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @param title the title of the corresponding merchant inventory, displayed * when the merchant inventory is viewed * @return a new merchant + * @deprecated The title parameter is no-longer needed when used with + * {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}. */ + @Deprecated(since = "1.21.4") @NotNull Merchant createMerchant(net.kyori.adventure.text.@Nullable Component title); // Paper start /** @@ -1700,7 +1704,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @param title the title of the corresponding merchant inventory, displayed * when the merchant inventory is viewed * @return a new merchant - * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)} + * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}, The title parameter is + * no-longer needed when used with {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}. */ @NotNull @Deprecated // Paper @@ -1715,6 +1720,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ int getMaxChainedNeighborUpdates(); + /** + * Creates an empty merchant. + * + * @return a new merchant + */ + @NotNull + Merchant createMerchant(); + /** * Gets user-specified limit for number of monsters that can spawn in a * chunk. diff --git a/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java b/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java index 6303c68b21..f04a6bf9e5 100644 --- a/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.MenuType; import org.bukkit.inventory.Merchant; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.FireworkMeta; @@ -126,7 +127,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#CRAFTING} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openWorkbench(@Nullable Location location, boolean force); @@ -140,7 +144,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * location, no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#ENCHANTMENT} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openEnchanting(@Nullable Location location, boolean force); @@ -166,8 +173,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * @param trader The merchant to trade with. Cannot be null. * @param force whether to force the trade even if another player is trading * @return The newly opened inventory view, or null if it could not be - * opened. + * @deprecated This method can be replaced by using {@link MenuType#MERCHANT} + * in conjunction with {@link #openInventory(InventoryView)}. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openMerchant(@NotNull Villager trader, boolean force); @@ -180,8 +189,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * @param merchant The merchant to trade with. Cannot be null. * @param force whether to force the trade even if another player is trading * @return The newly opened inventory view, or null if it could not be - * opened. + * @deprecated This method can be replaced by using {@link MenuType#MERCHANT} + * in conjunction with {@link #openInventory(InventoryView)}. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openMerchant(@NotNull Merchant merchant, boolean force); @@ -196,7 +207,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#ANVIL} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openAnvil(@Nullable Location location, boolean force); @@ -210,7 +224,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#CARTOGRAPHY_TABLE} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openCartographyTable(@Nullable Location location, boolean force); @@ -224,7 +241,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#GRINDSTONE} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openGrindstone(@Nullable Location location, boolean force); @@ -238,7 +258,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#LOOM} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openLoom(@Nullable Location location, boolean force); @@ -252,7 +275,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#SMITHING} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openSmithingTable(@Nullable Location location, boolean force); @@ -266,7 +292,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * no inventory will be opened and null will be returned. * @return The newly opened inventory view, or null if it could not be * opened. + * @deprecated This method should be replaced by {@link MenuType#STONECUTTER} + * see {@link MenuType.Typed#builder()} and its options for more information. */ + @Deprecated(since = "1.21.4") @Nullable public InventoryView openStonecutter(@Nullable Location location, boolean force); // Paper end diff --git a/paper-api/src/main/java/org/bukkit/inventory/MenuType.java b/paper-api/src/main/java/org/bukkit/inventory/MenuType.java index 529143c900..2442361ce7 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/MenuType.java +++ b/paper-api/src/main/java/org/bukkit/inventory/MenuType.java @@ -14,6 +14,9 @@ import org.bukkit.inventory.view.LecternView; import org.bukkit.inventory.view.LoomView; import org.bukkit.inventory.view.MerchantView; import org.bukkit.inventory.view.StonecutterView; +import org.bukkit.inventory.view.builder.InventoryViewBuilder; +import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder; +import org.bukkit.inventory.view.builder.MerchantInventoryViewBuilder; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -27,104 +30,104 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe /** * A MenuType which represents a chest with 1 row. */ - MenuType.Typed<InventoryView> GENERIC_9X1 = get("generic_9x1"); + MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X1 = get("generic_9x1"); /** * A MenuType which represents a chest with 2 rows. */ - MenuType.Typed<InventoryView> GENERIC_9X2 = get("generic_9x2"); + MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X2 = get("generic_9x2"); /** * A MenuType which represents a chest with 3 rows. */ - MenuType.Typed<InventoryView> GENERIC_9X3 = get("generic_9x3"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_9X3 = get("generic_9x3"); /** * A MenuType which represents a chest with 4 rows. */ - MenuType.Typed<InventoryView> GENERIC_9X4 = get("generic_9x4"); + MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X4 = get("generic_9x4"); /** * A MenuType which represents a chest with 5 rows. */ - MenuType.Typed<InventoryView> GENERIC_9X5 = get("generic_9x5"); + MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X5 = get("generic_9x5"); /** * A MenuType which represents a chest with 6 rows. */ - MenuType.Typed<InventoryView> GENERIC_9X6 = get("generic_9x6"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_9X6 = get("generic_9x6"); /** * A MenuType which represents a dispenser/dropper like menu with 3 columns * and 3 rows. */ - MenuType.Typed<InventoryView> GENERIC_3X3 = get("generic_3x3"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_3X3 = get("generic_3x3"); /** * A MenuType which represents a crafter */ - MenuType.Typed<CrafterView> CRAFTER_3X3 = get("crafter_3x3"); + MenuType.Typed<CrafterView, LocationInventoryViewBuilder<CrafterView>> CRAFTER_3X3 = get("crafter_3x3"); /** * A MenuType which represents an anvil. */ - MenuType.Typed<AnvilView> ANVIL = get("anvil"); + MenuType.Typed<AnvilView, LocationInventoryViewBuilder<AnvilView>> ANVIL = get("anvil"); /** * A MenuType which represents a beacon. */ - MenuType.Typed<BeaconView> BEACON = get("beacon"); + MenuType.Typed<BeaconView, LocationInventoryViewBuilder<BeaconView>> BEACON = get("beacon"); /** * A MenuType which represents a blast furnace. */ - MenuType.Typed<FurnaceView> BLAST_FURNACE = get("blast_furnace"); + MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> BLAST_FURNACE = get("blast_furnace"); /** * A MenuType which represents a brewing stand. */ - MenuType.Typed<BrewingStandView> BREWING_STAND = get("brewing_stand"); + MenuType.Typed<BrewingStandView, LocationInventoryViewBuilder<BrewingStandView>> BREWING_STAND = get("brewing_stand"); /** * A MenuType which represents a crafting table. */ - MenuType.Typed<InventoryView> CRAFTING = get("crafting"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> CRAFTING = get("crafting"); /** * A MenuType which represents an enchantment table. */ - MenuType.Typed<EnchantmentView> ENCHANTMENT = get("enchantment"); + MenuType.Typed<EnchantmentView, LocationInventoryViewBuilder<EnchantmentView>> ENCHANTMENT = get("enchantment"); /** * A MenuType which represents a furnace. */ - MenuType.Typed<FurnaceView> FURNACE = get("furnace"); + MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> FURNACE = get("furnace"); /** * A MenuType which represents a grindstone. */ - MenuType.Typed<InventoryView> GRINDSTONE = get("grindstone"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GRINDSTONE = get("grindstone"); /** * A MenuType which represents a hopper. */ - MenuType.Typed<InventoryView> HOPPER = get("hopper"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> HOPPER = get("hopper"); /** * A MenuType which represents a lectern, a book like view. */ - MenuType.Typed<LecternView> LECTERN = get("lectern"); + MenuType.Typed<LecternView, LocationInventoryViewBuilder<LecternView>> LECTERN = get("lectern"); /** * A MenuType which represents a loom. */ - MenuType.Typed<LoomView> LOOM = get("loom"); + MenuType.Typed<LoomView, LocationInventoryViewBuilder<LoomView>> LOOM = get("loom"); /** * A MenuType which represents a merchant. */ - MenuType.Typed<MerchantView> MERCHANT = get("merchant"); + MenuType.Typed<MerchantView, MerchantInventoryViewBuilder<MerchantView>> MERCHANT = get("merchant"); /** * A MenuType which represents a shulker box. */ - MenuType.Typed<InventoryView> SHULKER_BOX = get("shulker_box"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> SHULKER_BOX = get("shulker_box"); /** * A MenuType which represents a stonecutter. */ - MenuType.Typed<InventoryView> SMITHING = get("smithing"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> SMITHING = get("smithing"); /** * A MenuType which represents a smoker. */ - MenuType.Typed<FurnaceView> SMOKER = get("smoker"); + MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> SMOKER = get("smoker"); /** * A MenuType which represents a cartography table. */ - MenuType.Typed<InventoryView> CARTOGRAPHY_TABLE = get("cartography_table"); + MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> CARTOGRAPHY_TABLE = get("cartography_table"); /** * A MenuType which represents a stonecutter. */ - MenuType.Typed<StonecutterView> STONECUTTER = get("stonecutter"); + MenuType.Typed<StonecutterView, LocationInventoryViewBuilder<StonecutterView>> STONECUTTER = get("stonecutter"); /** * Typed represents a subtype of {@link MenuType}s that have a known @@ -133,7 +136,7 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe * @param <V> the generic type of {@link InventoryView} that represents the * view type. */ - interface Typed<V extends InventoryView> extends MenuType { + interface Typed<V extends InventoryView, B extends InventoryViewBuilder<V>> extends MenuType { /** * Creates a view of the specified menu type. @@ -166,6 +169,9 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe @NotNull V create(@NotNull HumanEntity player, @NotNull net.kyori.adventure.text.Component title); // Paper end - adventure + + @NotNull + B builder(); } // Paper start - adventure @@ -191,7 +197,7 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe * @return the typed MenuType. */ @NotNull - MenuType.Typed<InventoryView> typed(); + MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> typed(); /** * Yields this MenuType as a typed version of itself with a specific @@ -201,12 +207,14 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe * {@link InventoryView} with. * @param <V> the generic type of the InventoryView to get this MenuType * with + * @param <B> the generic type of the InventoryViewBuilder to get this + * MenuType with * @return the typed MenuType * @throws IllegalArgumentException if the provided viewClass cannot be * typed to this MenuType */ @NotNull - <V extends InventoryView> MenuType.Typed<V> typed(@NotNull final Class<V> viewClass) throws IllegalArgumentException; + <V extends InventoryView, B extends InventoryViewBuilder<V>> MenuType.Typed<V, B> typed(@NotNull final Class<V> viewClass) throws IllegalArgumentException; /** * Gets the {@link InventoryView} class of this MenuType. diff --git a/paper-api/src/main/java/org/bukkit/inventory/view/builder/InventoryViewBuilder.java b/paper-api/src/main/java/org/bukkit/inventory/view/builder/InventoryViewBuilder.java new file mode 100644 index 0000000000..9f07522283 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/inventory/view/builder/InventoryViewBuilder.java @@ -0,0 +1,38 @@ +package org.bukkit.inventory.view.builder; + +import net.kyori.adventure.text.Component; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.ApiStatus; + +/** + * Generic Builder for InventoryView's with no special attributes or parameters + * + * @param <V> the type of InventoryView created from this builder + */ +@ApiStatus.Experimental +public interface InventoryViewBuilder<V extends InventoryView> { + + /** + * Makes a copy of this builder + * + * @return a copy of this builder + */ + InventoryViewBuilder<V> copy(); + + /** + * Sets the title of the builder + * + * @param title the title + * @return this builder + */ + InventoryViewBuilder<V> title(final Component title); + + /** + * Builds this builder into a InventoryView + * + * @param player the player to assign to the view + * @return the created InventoryView + */ + V build(final HumanEntity player); +} diff --git a/paper-api/src/main/java/org/bukkit/inventory/view/builder/LocationInventoryViewBuilder.java b/paper-api/src/main/java/org/bukkit/inventory/view/builder/LocationInventoryViewBuilder.java new file mode 100644 index 0000000000..9666aa3569 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/inventory/view/builder/LocationInventoryViewBuilder.java @@ -0,0 +1,55 @@ +package org.bukkit.inventory.view.builder; + +import net.kyori.adventure.text.Component; +import org.bukkit.Location; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * An InventoryViewBuilder that can be bound by location within the world + * + * @param <V> the type of InventoryView created from this builder + */ +@ApiStatus.Experimental +public interface LocationInventoryViewBuilder<V extends InventoryView> extends InventoryViewBuilder<V> { + + @Override + LocationInventoryViewBuilder<V> copy(); + + @Override + LocationInventoryViewBuilder<V> title(final @NotNull Component title); + + /** + * Determines whether or not the server should check if the player can reach + * the location. + * <p> + * Not providing a location but setting checkReachable to true will + * automatically close the view when opened. + * <p> + * If checkReachable is set to false and a location is set on the builder if + * the target block exists and this builder is the correct menu for that + * block, e.g. MenuType.GENERIC_9X3 builder and target block set to chest, + * if that block is destroyed the view would persist. + * + * @param checkReachable whether or not to check if the view is "reachable" + * @return this builder + */ + LocationInventoryViewBuilder<V> checkReachable(final boolean checkReachable); + + /** + * Binds a location to this builder. + * <p> + * By binding a location in an unloaded chunk to this builder it is likely + * that the given chunk the location is will load. That means that when, + * building this view it may come with the costs associated with chunk + * loading. + * <p> + * Providing a location of a tile entity with a non matching menu comes with + * extra costs associated with ensuring that the correct view is created. + * + * @param location the location to bind to this view + * @return this builder + */ + LocationInventoryViewBuilder<V> location(final Location location); +} diff --git a/paper-api/src/main/java/org/bukkit/inventory/view/builder/MerchantInventoryViewBuilder.java b/paper-api/src/main/java/org/bukkit/inventory/view/builder/MerchantInventoryViewBuilder.java new file mode 100644 index 0000000000..76aecb54a9 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/inventory/view/builder/MerchantInventoryViewBuilder.java @@ -0,0 +1,44 @@ +package org.bukkit.inventory.view.builder; + +import net.kyori.adventure.text.Component; +import org.bukkit.Server; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Merchant; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * An InventoryViewBuilder for creating merchant views + * + * @param <V> the type of InventoryView created by this builder + */ +@ApiStatus.Experimental +public interface MerchantInventoryViewBuilder<V extends InventoryView> extends InventoryViewBuilder<V> { + + @Override + MerchantInventoryViewBuilder<V> copy(); + + @Override + MerchantInventoryViewBuilder<V> title(final @NotNull Component title); + + /** + * Adds a merchant to this builder + * + * @param merchant the merchant + * @return this builder + */ + MerchantInventoryViewBuilder<V> merchant(final Merchant merchant); + + /** + * Determines whether or not the server should check if the player can reach + * the location. + * <p> + * Given checkReachable is provided and a virtual merchant is provided to + * the builder from {@link Server#createMerchant(net.kyori.adventure.text.Component)} this method will + * have no effect on the actual menu status. + * + * @param checkReachable whether or not to check if the view is "reachable" + * @return this builder + */ + MerchantInventoryViewBuilder<V> checkReachable(final boolean checkReachable); +} diff --git a/paper-api/src/main/java/org/bukkit/inventory/view/builder/package-info.java b/paper-api/src/main/java/org/bukkit/inventory/view/builder/package-info.java new file mode 100644 index 0000000000..b1e4203dae --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/inventory/view/builder/package-info.java @@ -0,0 +1,9 @@ +/** + * A Package that contains builders for building InventoryViews. + */ +@NullMarked +@ApiStatus.Experimental +package org.bukkit.inventory.view.builder; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch index e33c611feb..6b7e7d70cb 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch @@ -9,7 +9,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.Container; -@@ -63,6 +_,31 @@ +@@ -63,6 +_,32 @@ @Nullable private ContainerSynchronizer synchronizer; private boolean suppressRemoteUpdates; @@ -37,6 +37,7 @@ + com.google.common.base.Preconditions.checkState(this.title == null, "Title already set"); + this.title = title; + } ++ public void startOpen() {} + // CraftBukkit end protected AbstractContainerMenu(@Nullable MenuType<?> menuType, int containerId) { diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch index c86ead27cc..1be2d1bc41 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/inventory/ChestMenu.java +++ b/net/minecraft/world/inventory/ChestMenu.java -@@ -9,6 +_,29 @@ +@@ -9,6 +_,34 @@ public class ChestMenu extends AbstractContainerMenu { private final Container container; private final int containerRows; @@ -26,14 +26,21 @@ + this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this); + return this.bukkitEntity; + } ++ ++ @Override ++ public void startOpen() { ++ this.container.startOpen(this.player.player); ++ } + // CraftBukkit end private ChestMenu(MenuType<?> type, int containerId, Inventory playerInventory, int rows) { this(type, containerId, playerInventory, new SimpleContainer(9 * rows), rows); -@@ -52,6 +_,9 @@ +@@ -51,7 +_,10 @@ + checkContainerSize(container, rows * 9); this.container = container; this.containerRows = rows; - container.startOpen(playerInventory.player); +- container.startOpen(playerInventory.player); ++ // container.startOpen(playerInventory.player); // Paper - don't startOpen until menu actually opens + // CraftBukkit start - Save player + this.player = playerInventory; + // CraftBukkit end diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch index d3c402326c..f1c3c78a5b 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch @@ -27,6 +27,14 @@ this.addStandardInventorySlots(playerInventory, 108, 84); } +@@ -61,6 +_,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!checkReachable) return true; // Paper - checkReachable + return this.trader.stillValid(player); + } + @@ -105,12 +_,12 @@ ItemStack item = slot.getItem(); itemStack = item.copy(); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch index a65b4c24dc..590e2788dd 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/inventory/ShulkerBoxMenu.java +++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java -@@ -9,6 +_,20 @@ +@@ -9,6 +_,25 @@ public class ShulkerBoxMenu extends AbstractContainerMenu { private static final int CONTAINER_SIZE = 27; private final Container container; @@ -17,18 +17,25 @@ + this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventory(this.container), this); + return this.bukkitEntity; + } ++ ++ @Override ++ public void startOpen() { ++ container.startOpen(player.player); ++ } + // CraftBukkit end public ShulkerBoxMenu(int containerId, Inventory playerInventory) { this(containerId, playerInventory, new SimpleContainer(27)); -@@ -18,6 +_,7 @@ +@@ -18,7 +_,8 @@ super(MenuType.SHULKER_BOX, containerId); checkContainerSize(container, 27); this.container = container; +- container.startOpen(playerInventory.player); + this.player = playerInventory; // CraftBukkit - save player - container.startOpen(playerInventory.player); ++ // container.startOpen(playerInventory.player); // Paper - don't startOpen until menu actually opens int i = 3; int i1 = 9; + @@ -33,6 +_,7 @@ @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index fda9daa636..d2de789967 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -258,6 +258,7 @@ import org.bukkit.scoreboard.Criteria; import org.bukkit.structure.StructureManager; import org.bukkit.util.StringUtil; import org.bukkit.util.permissions.DefaultPermissions; +import org.jetbrains.annotations.NotNull; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -2485,6 +2486,11 @@ public final class CraftServer implements Server { return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); } + @Override + public @NotNull Merchant createMerchant() { + return new CraftMerchantCustom(net.kyori.adventure.text.Component.empty()); + } + @Override public int getMaxChainedNeighborUpdates() { return this.getServer().getMaxChainedNeighborUpdates(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index a1f42f860f..cafd8c5349 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -25,6 +25,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.FireworkRocketEntity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.MerchantMenu; import net.minecraft.world.item.ItemCooldowns; import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeManager; @@ -50,6 +51,8 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryView; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftMerchantCustom; import org.bukkit.craftbukkit.inventory.CraftRecipe; +import org.bukkit.craftbukkit.inventory.util.CraftMenus; +import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.entity.Firework; import org.bukkit.entity.HumanEntity; @@ -467,6 +470,11 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { // Now open the window MenuType<?> windowType = CraftContainer.getNotchInventoryType(inventory.getTopInventory()); + // we can open these now, delegate for now + if (windowType == MenuType.MERCHANT) { + CraftMenus.openMerchantMenu(player, (MerchantMenu) container); + return; + } //String title = inventory.getTitle(); // Paper - comment net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index e37aaf77f9..d7a52220e9 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -1430,6 +1430,7 @@ public class CraftEventFactory { } public static com.mojang.datafixers.util.Pair<net.kyori.adventure.text.@org.jetbrains.annotations.Nullable Component, @org.jetbrains.annotations.Nullable AbstractContainerMenu> callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { // Paper end - Add titleOverride to InventoryOpenEvent + container.startOpen(); // delegate start open logic to before InventoryOpenEvent is fired if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 6d3f9d5dab..1ce328bed5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -132,7 +132,7 @@ public class CraftContainer extends AbstractContainerMenu { if (menu == null) { return net.minecraft.world.inventory.MenuType.GENERIC_9x3; } else { - return ((CraftMenuType<?>) menu).getHandle(); + return ((CraftMenuType<?, ?>) menu).getHandle(); } } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java index e4d81ef26a..4c6cf43cee 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java @@ -15,12 +15,14 @@ import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.MenuType; +import org.bukkit.inventory.view.builder.InventoryViewBuilder; +import org.jetbrains.annotations.NotNull; -public class CraftMenuType<V extends InventoryView> implements MenuType.Typed<V>, Handleable<net.minecraft.world.inventory.MenuType<?>>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - make FeatureDependant +public class CraftMenuType<V extends InventoryView, B extends InventoryViewBuilder<V>> implements MenuType.Typed<V, B>, Handleable<net.minecraft.world.inventory.MenuType<?>>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - make FeatureDependant private final NamespacedKey key; private final net.minecraft.world.inventory.MenuType<?> handle; - private final Supplier<CraftMenus.MenuTypeData<V>> typeData; + private final Supplier<CraftMenus.MenuTypeData<V, B>> typeData; public CraftMenuType(NamespacedKey key, net.minecraft.world.inventory.MenuType<?> handle) { this.key = key; @@ -36,33 +38,28 @@ public class CraftMenuType<V extends InventoryView> implements MenuType.Typed<V> @Override public V create(final HumanEntity player, final String title) { // Paper start - adventure - return create(player, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(title)); + return builder().title(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(title)).build(player); } @Override public V create(final HumanEntity player, final net.kyori.adventure.text.Component title) { // Paper end - adventure - Preconditions.checkArgument(player != null, "The given player must not be null"); - Preconditions.checkArgument(title != null, "The given title must not be null"); - Preconditions.checkArgument(player instanceof CraftHumanEntity, "The given player must be a CraftHumanEntity"); - final CraftHumanEntity craftHuman = (CraftHumanEntity) player; - Preconditions.checkArgument(craftHuman.getHandle() instanceof ServerPlayer, "The given player must be an EntityPlayer"); - final ServerPlayer serverPlayer = (ServerPlayer) craftHuman.getHandle(); - - final AbstractContainerMenu container = this.typeData.get().menuBuilder().build(serverPlayer, this.handle); - container.setTitle(io.papermc.paper.adventure.PaperAdventure.asVanilla(title)); // Paper - adventure - container.checkReachable = false; - return (V) container.getBukkitView(); + return builder().title(title).build(player); } @Override - public Typed<InventoryView> typed() { + public B builder() { + return typeData.get().viewBuilder().get(); + } + + @Override + public Typed<InventoryView, InventoryViewBuilder<InventoryView>> typed() { return this.typed(InventoryView.class); } @Override - public <V extends InventoryView> Typed<V> typed(Class<V> clazz) { + public <V extends InventoryView, B extends InventoryViewBuilder<V>> Typed<V, B> typed(Class<V> clazz) { if (clazz.isAssignableFrom(this.typeData.get().viewClass())) { - return (Typed<V>) this; + return (Typed<V, B>) this; } throw new IllegalArgumentException("Cannot type InventoryView " + this.key.toString() + " to InventoryView type " + clazz.getSimpleName()); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java index 66e93f8444..84c35792c4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java @@ -1,16 +1,18 @@ package org.bukkit.craftbukkit.inventory.util; -import static org.bukkit.craftbukkit.inventory.util.CraftMenuBuilder.*; - -import net.minecraft.network.chat.Component; -import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.inventory.AnvilMenu; import net.minecraft.world.inventory.CartographyTableMenu; import net.minecraft.world.inventory.CraftingMenu; import net.minecraft.world.inventory.EnchantmentMenu; import net.minecraft.world.inventory.GrindstoneMenu; +import net.minecraft.world.inventory.MerchantMenu; import net.minecraft.world.inventory.SmithingMenu; import net.minecraft.world.inventory.StonecutterMenu; +import net.minecraft.world.item.trading.Merchant; +import net.minecraft.world.item.trading.MerchantOffers; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BeaconBlockEntity; import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; @@ -20,8 +22,15 @@ import net.minecraft.world.level.block.entity.DispenserBlockEntity; import net.minecraft.world.level.block.entity.FurnaceBlockEntity; import net.minecraft.world.level.block.entity.HopperBlockEntity; import net.minecraft.world.level.block.entity.LecternBlockEntity; +import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; import net.minecraft.world.level.block.entity.SmokerBlockEntity; import org.bukkit.craftbukkit.inventory.CraftMenuType; +import org.bukkit.craftbukkit.inventory.CraftMerchant; +import org.bukkit.craftbukkit.inventory.view.builder.CraftAccessLocationInventoryViewBuilder; +import org.bukkit.craftbukkit.inventory.view.builder.CraftBlockEntityInventoryViewBuilder; +import org.bukkit.craftbukkit.inventory.view.builder.CraftDoubleChestInventoryViewBuilder; +import org.bukkit.craftbukkit.inventory.view.builder.CraftMerchantInventoryViewBuilder; +import org.bukkit.craftbukkit.inventory.view.builder.CraftStandardInventoryViewBuilder; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.MenuType; import org.bukkit.inventory.view.AnvilView; @@ -34,83 +43,120 @@ import org.bukkit.inventory.view.LecternView; import org.bukkit.inventory.view.LoomView; import org.bukkit.inventory.view.MerchantView; import org.bukkit.inventory.view.StonecutterView; +import org.bukkit.inventory.view.builder.InventoryViewBuilder; +import org.jspecify.annotations.NullMarked; +import java.util.function.Supplier; + +@NullMarked public final class CraftMenus { - public record MenuTypeData<V extends InventoryView>(Class<V> viewClass, CraftMenuBuilder menuBuilder) { + public record MenuTypeData<V extends InventoryView, B extends InventoryViewBuilder<V>>(Class<V> viewClass, Supplier<B> viewBuilder) { } - private static final CraftMenuBuilder STANDARD = (player, menuType) -> menuType.create(player.nextContainerCounter(), player.getInventory()); + // This is a temporary measure that will likely be removed with the rewrite of HumanEntity#open[] methods + public static void openMerchantMenu(final ServerPlayer player, final MerchantMenu merchant) { + final Merchant minecraftMerchant = ((CraftMerchant) merchant.getBukkitView().getMerchant()).getMerchant(); + int level = 1; + if (minecraftMerchant instanceof final Villager villager) { + level = villager.getVillagerData().getLevel(); + } - public static <V extends InventoryView> MenuTypeData<V> getMenuTypeData(CraftMenuType<?> menuType) { + if (minecraftMerchant.getTradingPlayer() != null) { // merchant's can only have one trader + minecraftMerchant.getTradingPlayer().closeContainer(); + } + + minecraftMerchant.setTradingPlayer(player); + + player.connection.send(new ClientboundOpenScreenPacket(merchant.containerId, net.minecraft.world.inventory.MenuType.MERCHANT, merchant.getTitle())); + player.containerMenu = merchant; + player.initMenu(merchant); + // Copy IMerchant#openTradingScreen + MerchantOffers merchantrecipelist = minecraftMerchant.getOffers(); + + if (!merchantrecipelist.isEmpty()) { + player.sendMerchantOffers(merchant.containerId, merchantrecipelist, level, minecraftMerchant.getVillagerXp(), minecraftMerchant.showProgressBar(), minecraftMerchant.canRestock()); + } + // End Copy IMerchant#openTradingScreen + } + + public static <V extends InventoryView, B extends InventoryViewBuilder<V>> MenuTypeData<V, B> getMenuTypeData(final CraftMenuType<?, ?> menuType) { + final net.minecraft.world.inventory.MenuType<?> handle = menuType.getHandle(); + // this sucks horribly but it should work for now + if (menuType == MenuType.GENERIC_9X6) { + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftDoubleChestInventoryViewBuilder<>(handle))); + } + if (menuType == MenuType.GENERIC_9X3) { + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.CHEST, null))); + } // this isn't ideal as both dispenser and dropper are 3x3, InventoryType can't currently handle generic 3x3s with size 9 // this needs to be removed when inventory creation is overhauled if (menuType == MenuType.GENERIC_3X3) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, tileEntity(DispenserBlockEntity::new, Blocks.DISPENSER))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.DISPENSER, DispenserBlockEntity::new))); } if (menuType == MenuType.CRAFTER_3X3) { - return CraftMenus.asType(new MenuTypeData<>(CrafterView.class, tileEntity(CrafterBlockEntity::new, Blocks.CRAFTER))); + return asType(new MenuTypeData<>(CrafterView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.CRAFTER, CrafterBlockEntity::new))); } if (menuType == MenuType.ANVIL) { - return CraftMenus.asType(new MenuTypeData<>(AnvilView.class, worldAccess(AnvilMenu::new))); + return asType(new MenuTypeData<>(AnvilView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, AnvilMenu::new))); } if (menuType == MenuType.BEACON) { - return CraftMenus.asType(new MenuTypeData<>(BeaconView.class, tileEntity(BeaconBlockEntity::new, Blocks.BEACON))); + return asType(new MenuTypeData<>(BeaconView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.BEACON, BeaconBlockEntity::new))); } if (menuType == MenuType.BLAST_FURNACE) { - return CraftMenus.asType(new MenuTypeData<>(FurnaceView.class, tileEntity(BlastFurnaceBlockEntity::new, Blocks.BLAST_FURNACE))); + return asType(new MenuTypeData<>(FurnaceView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.BLAST_FURNACE, BlastFurnaceBlockEntity::new))); } if (menuType == MenuType.BREWING_STAND) { - return CraftMenus.asType(new MenuTypeData<>(BrewingStandView.class, tileEntity(BrewingStandBlockEntity::new, Blocks.BREWING_STAND))); + return asType(new MenuTypeData<>(BrewingStandView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.BREWING_STAND, BrewingStandBlockEntity::new))); } if (menuType == MenuType.CRAFTING) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, worldAccess(CraftingMenu::new))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, CraftingMenu::new))); } if (menuType == MenuType.ENCHANTMENT) { - return CraftMenus.asType(new MenuTypeData<>(EnchantmentView.class, (player, type) -> { - return new SimpleMenuProvider((syncId, inventory, human) -> { - return worldAccess(EnchantmentMenu::new).build(player, type); - }, Component.empty()).createMenu(player.nextContainerCounter(), player.getInventory(), player); - })); + return asType(new MenuTypeData<>(EnchantmentView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, EnchantmentMenu::new))); } if (menuType == MenuType.FURNACE) { - return CraftMenus.asType(new MenuTypeData<>(FurnaceView.class, tileEntity(FurnaceBlockEntity::new, Blocks.FURNACE))); + return asType(new MenuTypeData<>(FurnaceView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.FURNACE, FurnaceBlockEntity::new))); } if (menuType == MenuType.GRINDSTONE) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, worldAccess(GrindstoneMenu::new))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, GrindstoneMenu::new))); } // We really don't need to be creating a tile entity for hopper but currently InventoryType doesn't have capacity // to understand otherwise if (menuType == MenuType.HOPPER) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, tileEntity(HopperBlockEntity::new, Blocks.HOPPER))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.HOPPER, HopperBlockEntity::new))); } // We also don't need to create a tile entity for lectern, but again InventoryType isn't smart enough to know any better if (menuType == MenuType.LECTERN) { - return CraftMenus.asType(new MenuTypeData<>(LecternView.class, tileEntity(LecternBlockEntity::new, Blocks.LECTERN))); + return asType(new MenuTypeData<>(LecternView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.LECTERN, LecternBlockEntity::new))); } if (menuType == MenuType.LOOM) { - return CraftMenus.asType(new MenuTypeData<>(LoomView.class, CraftMenus.STANDARD)); + return asType(new MenuTypeData<>(LoomView.class, () -> new CraftStandardInventoryViewBuilder<>(handle))); } if (menuType == MenuType.MERCHANT) { - return CraftMenus.asType(new MenuTypeData<>(MerchantView.class, CraftMenus.STANDARD)); + return asType(new MenuTypeData<>(MerchantView.class, () -> new CraftMerchantInventoryViewBuilder<>(handle))); + } + if (menuType == MenuType.SHULKER_BOX) { + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.SHULKER_BOX, ShulkerBoxBlockEntity::new))); } if (menuType == MenuType.SMITHING) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, worldAccess(SmithingMenu::new))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, SmithingMenu::new))); } if (menuType == MenuType.SMOKER) { - return CraftMenus.asType(new MenuTypeData<>(FurnaceView.class, tileEntity(SmokerBlockEntity::new, Blocks.SMOKER))); + return asType(new MenuTypeData<>(FurnaceView.class, () -> new CraftBlockEntityInventoryViewBuilder<>(handle, Blocks.SMOKER, SmokerBlockEntity::new))); } if (menuType == MenuType.CARTOGRAPHY_TABLE) { - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, worldAccess(CartographyTableMenu::new))); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, CartographyTableMenu::new))); } if (menuType == MenuType.STONECUTTER) { - return CraftMenus.asType(new MenuTypeData<>(StonecutterView.class, worldAccess(StonecutterMenu::new))); + return asType(new MenuTypeData<>(StonecutterView.class, () -> new CraftAccessLocationInventoryViewBuilder<>(handle, StonecutterMenu::new))); } - return CraftMenus.asType(new MenuTypeData<>(InventoryView.class, CraftMenus.STANDARD)); + return asType(new MenuTypeData<>(InventoryView.class, () -> new CraftStandardInventoryViewBuilder<>(handle))); } - private static <V extends InventoryView> MenuTypeData<V> asType(MenuTypeData<?> data) { - return (MenuTypeData<V>) data; + @SuppressWarnings("unchecked") + private static <V extends InventoryView, B extends InventoryViewBuilder<V>> MenuTypeData<V, B> asType(final MenuTypeData<?, ?> data) { + return (MenuTypeData<V, B>) data; } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractInventoryViewBuilder.java new file mode 100644 index 0000000000..185ad0fc16 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractInventoryViewBuilder.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import com.google.common.base.Preconditions; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.InventoryViewBuilder; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +public abstract class CraftAbstractInventoryViewBuilder<V extends InventoryView> implements InventoryViewBuilder<V> { + + protected final MenuType<?> handle; + + protected boolean checkReachable = false; + protected @MonotonicNonNull Component title = null; + + public CraftAbstractInventoryViewBuilder(final MenuType<?> handle) { + this.handle = handle; + } + + @Override + public InventoryViewBuilder<V> title(final Component title) { + this.title = title; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public V build(final HumanEntity player) { + Preconditions.checkArgument(player != null, "The given player must not be null"); + Preconditions.checkArgument(this.title != null, "The given title must not be null"); + Preconditions.checkArgument(player instanceof CraftHumanEntity, "The given player must be a CraftHumanEntity"); + final CraftHumanEntity craftHuman = (CraftHumanEntity) player; + Preconditions.checkArgument(craftHuman.getHandle() instanceof ServerPlayer, "The given player must be an EntityPlayer"); + final ServerPlayer serverPlayer = (ServerPlayer) craftHuman.getHandle(); + final AbstractContainerMenu container = buildContainer(serverPlayer); + container.checkReachable = this.checkReachable; + container.setTitle(PaperAdventure.asVanilla(this.title)); + return (V) container.getBukkitView(); + } + + protected abstract AbstractContainerMenu buildContainer(ServerPlayer player); +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractLocationInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractLocationInventoryViewBuilder.java new file mode 100644 index 0000000000..7a894ca078 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAbstractLocationInventoryViewBuilder.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.Component; +import net.minecraft.core.BlockPos; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.Level; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftLocation; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder; +import org.jspecify.annotations.Nullable; + +public abstract class CraftAbstractLocationInventoryViewBuilder<V extends InventoryView> extends CraftAbstractInventoryViewBuilder<V> implements LocationInventoryViewBuilder<V> { + + protected @Nullable Level world; + protected @Nullable BlockPos position; + + public CraftAbstractLocationInventoryViewBuilder(final MenuType<?> handle) { + super(handle); + } + + @Override + public LocationInventoryViewBuilder<V> title(final Component title) { + return (LocationInventoryViewBuilder<V>) super.title(title); + } + + @Override + public LocationInventoryViewBuilder<V> copy() { + throw new UnsupportedOperationException("copy is not implemented on CraftAbstractLocationInventoryViewBuilder"); + } + + @Override + public LocationInventoryViewBuilder<V> checkReachable(final boolean checkReachable) { + super.checkReachable = checkReachable; + return this; + } + + @Override + public LocationInventoryViewBuilder<V> location(final Location location) { + Preconditions.checkArgument(location != null, "The provided location must not be null"); + Preconditions.checkArgument(location.getWorld() != null, "The provided location must be associated with a world"); + this.world = ((CraftWorld) location.getWorld()).getHandle(); + this.position = CraftLocation.toBlockPosition(location); + return this; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAccessLocationInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAccessLocationInventoryViewBuilder.java new file mode 100644 index 0000000000..096f3ebf81 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftAccessLocationInventoryViewBuilder.java @@ -0,0 +1,45 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.MenuType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder; + +public class CraftAccessLocationInventoryViewBuilder<V extends InventoryView> extends CraftAbstractLocationInventoryViewBuilder<V> { + + private final CraftAccessContainerObjectBuilder containerBuilder; + + public CraftAccessLocationInventoryViewBuilder(final MenuType<?> handle, final CraftAccessContainerObjectBuilder containerBuilder) { + super(handle); + this.containerBuilder = containerBuilder; + } + + @Override + protected AbstractContainerMenu buildContainer(final ServerPlayer player) { + final ContainerLevelAccess access; + if (super.position == null) { + access = ContainerLevelAccess.create(player.level(), player.blockPosition()); + } else { + access = ContainerLevelAccess.create(super.world, super.position); + } + + return this.containerBuilder.build(player.nextContainerCounter(), player.getInventory(), access); + } + + @Override + public LocationInventoryViewBuilder<V> copy() { + final CraftAccessLocationInventoryViewBuilder<V> copy = new CraftAccessLocationInventoryViewBuilder<>(this.handle, this.containerBuilder); + copy.world = super.world; + copy.position = super.position; + copy.checkReachable = super.checkReachable; + copy.title = title; + return copy; + } + + public interface CraftAccessContainerObjectBuilder { + AbstractContainerMenu build(final int syncId, final Inventory inventory, ContainerLevelAccess access); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftBlockEntityInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftBlockEntityInventoryViewBuilder.java new file mode 100644 index 0000000000..2625814440 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftBlockEntityInventoryViewBuilder.java @@ -0,0 +1,74 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuConstructor; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder; +import org.jspecify.annotations.Nullable; + +public class CraftBlockEntityInventoryViewBuilder<V extends InventoryView> extends CraftAbstractLocationInventoryViewBuilder<V> { + + private final Block block; + private final @Nullable CraftTileInventoryBuilder builder; + + public CraftBlockEntityInventoryViewBuilder(final MenuType<?> handle, final Block block, final @Nullable CraftTileInventoryBuilder builder) { + super(handle); + this.block = block; + this.builder = builder; + } + + @Override + protected AbstractContainerMenu buildContainer(final ServerPlayer player) { + if (this.world == null) { + this.world = player.level(); + } + + if (this.position == null) { + this.position = player.blockPosition(); + } + + final BlockEntity entity = this.world.getBlockEntity(position); + if (!(entity instanceof final MenuConstructor container)) { + return buildFakeTile(player); + } + + final AbstractContainerMenu atBlock = container.createMenu(player.nextContainerCounter(), player.getInventory(), player); + if (atBlock.getType() != super.handle) { + return buildFakeTile(player); + } + + return atBlock; + } + + private AbstractContainerMenu buildFakeTile(final ServerPlayer player) { + if (this.builder == null) { + return handle.create(player.nextContainerCounter(), player.getInventory()); + } + final MenuProvider inventory = this.builder.build(this.position, this.block.defaultBlockState()); + if (inventory instanceof final BlockEntity tile) { + tile.setLevel(this.world); + } + return inventory.createMenu(player.nextContainerCounter(), player.getInventory(), player); + } + + @Override + public LocationInventoryViewBuilder<V> copy() { + final CraftBlockEntityInventoryViewBuilder<V> copy = new CraftBlockEntityInventoryViewBuilder<>(super.handle, this.block, this.builder); + copy.world = this.world; + copy.position = this.position; + copy.checkReachable = super.checkReachable; + copy.title = title; + return copy; + } + + public interface CraftTileInventoryBuilder { + MenuProvider build(BlockPos blockPosition, BlockState blockData); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftDoubleChestInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftDoubleChestInventoryViewBuilder.java new file mode 100644 index 0000000000..331e3797a5 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftDoubleChestInventoryViewBuilder.java @@ -0,0 +1,48 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.DoubleBlockCombiner; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder; + +public class CraftDoubleChestInventoryViewBuilder<V extends InventoryView> extends CraftAbstractLocationInventoryViewBuilder<V> { + + public CraftDoubleChestInventoryViewBuilder(final MenuType<?> handle) { + super(handle); + } + + @Override + protected AbstractContainerMenu buildContainer(final ServerPlayer player) { + if (super.world == null) { + return handle.create(player.nextContainerCounter(), player.getInventory()); + } + + final ChestBlock chest = (ChestBlock) Blocks.CHEST; + final DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> result = chest.combine(super.world.getBlockState(super.position), super.world, super.position, false); + if (result instanceof DoubleBlockCombiner.NeighborCombineResult.Single<? extends ChestBlockEntity>) { + return handle.create(player.nextContainerCounter(), player.getInventory()); + } + + final MenuProvider combined = result.apply(ChestBlock.MENU_PROVIDER_COMBINER).orElse(null); + if (combined == null) { + return handle.create(player.nextContainerCounter(), player.getInventory()); + } + return combined.createMenu(player.nextContainerCounter(), player.getInventory(), player); + } + + @Override + public LocationInventoryViewBuilder<V> copy() { + final CraftDoubleChestInventoryViewBuilder<V> copy = new CraftDoubleChestInventoryViewBuilder<>(super.handle); + copy.world = this.world; + copy.position = this.position; + copy.checkReachable = super.checkReachable; + copy.title = title; + return copy; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftMerchantInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftMerchantInventoryViewBuilder.java new file mode 100644 index 0000000000..7f7518aa73 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftMerchantInventoryViewBuilder.java @@ -0,0 +1,78 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import com.google.common.base.Preconditions; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.MerchantMenu; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.inventory.CraftMerchant; +import org.bukkit.craftbukkit.inventory.CraftMerchantCustom; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.view.builder.MerchantInventoryViewBuilder; +import org.jspecify.annotations.Nullable; + +public class CraftMerchantInventoryViewBuilder<V extends InventoryView> extends CraftAbstractInventoryViewBuilder<V> implements MerchantInventoryViewBuilder<V> { + + private net.minecraft.world.item.trading.@Nullable Merchant merchant; + + public CraftMerchantInventoryViewBuilder(final MenuType<?> handle) { + super(handle); + } + + @Override + public MerchantInventoryViewBuilder<V> title(final Component title) { + return (MerchantInventoryViewBuilder<V>) super.title(title); + } + + @Override + public MerchantInventoryViewBuilder<V> merchant(final Merchant merchant) { + this.merchant = ((CraftMerchant) merchant).getMerchant(); + return this; + } + + @Override + public MerchantInventoryViewBuilder<V> checkReachable(final boolean checkReachable) { + super.checkReachable = checkReachable; + return this; + } + + @Override + public V build(final HumanEntity player) { + Preconditions.checkArgument(player != null, "The given player must not be null"); + Preconditions.checkArgument(this.title != null, "The given title must not be null"); + Preconditions.checkArgument(player instanceof CraftHumanEntity, "The given player must be a CraftHumanEntity"); + final CraftHumanEntity craftHuman = (CraftHumanEntity) player; + Preconditions.checkArgument(craftHuman.getHandle() instanceof ServerPlayer, "The given player must be an EntityPlayer"); + final ServerPlayer serverPlayer = (ServerPlayer) craftHuman.getHandle(); + + final MerchantMenu container; + if (this.merchant == null) { + container = new MerchantMenu(serverPlayer.nextContainerCounter(), serverPlayer.getInventory(), new CraftMerchantCustom(title).getMerchant()); + } else { + container = new MerchantMenu(serverPlayer.nextContainerCounter(), serverPlayer.getInventory(), this.merchant); + } + + container.checkReachable = super.checkReachable; + container.setTitle(PaperAdventure.asVanilla(this.title)); + return (V) container.getBukkitView(); + } + + @Override + protected AbstractContainerMenu buildContainer(final ServerPlayer player) { + throw new UnsupportedOperationException("buildContainer is not supported for CraftMerchantInventoryViewBuilder"); + } + + @Override + public MerchantInventoryViewBuilder<V> copy() { + final CraftMerchantInventoryViewBuilder<V> copy = new CraftMerchantInventoryViewBuilder<>(super.handle); + copy.checkReachable = super.checkReachable; + copy.merchant = this.merchant; + copy.title = title; + return copy; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftStandardInventoryViewBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftStandardInventoryViewBuilder.java new file mode 100644 index 0000000000..e528facbe0 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/CraftStandardInventoryViewBuilder.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.inventory.view.builder; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.view.builder.InventoryViewBuilder; + +public class CraftStandardInventoryViewBuilder<V extends InventoryView> extends CraftAbstractInventoryViewBuilder<V> { + + public CraftStandardInventoryViewBuilder(final MenuType<?> handle) { + super(handle); + } + + @Override + protected AbstractContainerMenu buildContainer(final ServerPlayer player) { + return super.handle.create(player.nextContainerCounter(), player.getInventory()); + } + + @Override + public InventoryViewBuilder<V> copy() { + final CraftStandardInventoryViewBuilder<V> copy = new CraftStandardInventoryViewBuilder<>(handle); + copy.title = this.title; + return copy; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/package-info.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/package-info.java new file mode 100644 index 0000000000..157ce9fd75 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/view/builder/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.bukkit.craftbukkit.inventory.view.builder; + +import org.jspecify.annotations.NullMarked;