MenuType API addition InventoryView Builders (#11816)

This commit is contained in:
Miles
2025-01-11 19:21:24 +00:00
committed by GitHub
parent 775002a357
commit c94922514a
27 changed files with 759 additions and 95 deletions

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;