1
0
Fork 0
mirror of https://github.com/PaperMC/Paper.git synced 2025-01-14 21:53:57 +01:00

MenuType API addition InventoryView Builders ()

This commit is contained in:
Miles 2025-01-11 19:21:24 +00:00 committed by GitHub
parent 775002a357
commit c94922514a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 759 additions and 95 deletions

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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
}

View file

@ -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();
}
}
}

View file

@ -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());

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,4 @@
@NullMarked
package org.bukkit.craftbukkit.inventory.view.builder;
import org.jspecify.annotations.NullMarked;