From fbea3cdc0caca69814e5ab68b981fa0bdbe5331d Mon Sep 17 00:00:00 2001 From: CraftBukkit/Spigot Date: Sat, 7 Sep 2024 18:52:00 +1000 Subject: [PATCH] #1458: Add MenuType API By: Miles Holder --- .../org/bukkit/craftbukkit/CraftRegistry.java | 5 + .../craftbukkit/inventory/CraftContainer.java | 55 ++------- .../craftbukkit/inventory/CraftMenuType.java | 89 ++++++++++++++ .../inventory/util/CraftMenuBuilder.java | 38 ++++++ .../inventory/util/CraftMenus.java | 115 ++++++++++++++++++ .../provider/RegistriesArgumentProvider.java | 4 + 6 files changed, 260 insertions(+), 46 deletions(-) create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenuBuilder.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java index b550d3f0f6..2ae0b01892 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java @@ -33,6 +33,7 @@ import org.bukkit.craftbukkit.entity.CraftWolf; import org.bukkit.craftbukkit.generator.structure.CraftStructure; import org.bukkit.craftbukkit.generator.structure.CraftStructureType; import org.bukkit.craftbukkit.inventory.CraftItemType; +import org.bukkit.craftbukkit.inventory.CraftMenuType; import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; import org.bukkit.craftbukkit.legacy.FieldRename; @@ -51,6 +52,7 @@ import org.bukkit.entity.Wolf; import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.StructureType; import org.bukkit.inventory.ItemType; +import org.bukkit.inventory.MenuType; import org.bukkit.inventory.meta.trim.TrimMaterial; import org.bukkit.inventory.meta.trim.TrimPattern; import org.bukkit.map.MapCursor; @@ -138,6 +140,9 @@ public class CraftRegistry implements Registry { if (bukkitClass == MusicInstrument.class) { return new CraftRegistry<>(MusicInstrument.class, registryHolder.registryOrThrow(Registries.INSTRUMENT), CraftMusicInstrument::new, FieldRename.NONE); } + if (bukkitClass == MenuType.class) { + return new CraftRegistry<>(MenuType.class, registryHolder.registryOrThrow(Registries.MENU), CraftMenuType::new, FieldRename.NONE); + } if (bukkitClass == PotionEffectType.class) { return new CraftRegistry<>(PotionEffectType.class, registryHolder.registryOrThrow(Registries.MOB_EFFECT), CraftPotionEffectType::new, FieldRename.NONE); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 6ad2e399e3..576ed605b3 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -30,6 +30,7 @@ import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.MenuType; public class CraftContainer extends Container { @@ -98,7 +99,8 @@ public class CraftContainer extends Container { } public static Containers getNotchInventoryType(Inventory inventory) { - switch (inventory.getType()) { + final InventoryType type = inventory.getType(); + switch (type) { case PLAYER: case CHEST: case ENDER_CHEST: @@ -120,52 +122,13 @@ public class CraftContainer extends Container { default: throw new IllegalArgumentException("Unsupported custom inventory size " + inventory.getSize()); } - case WORKBENCH: - return Containers.CRAFTING; - case FURNACE: - return Containers.FURNACE; - case DISPENSER: - return Containers.GENERIC_3x3; - case ENCHANTING: - return Containers.ENCHANTMENT; - case BREWING: - return Containers.BREWING_STAND; - case BEACON: - return Containers.BEACON; - case ANVIL: - return Containers.ANVIL; - case HOPPER: - return Containers.HOPPER; - case DROPPER: - return Containers.GENERIC_3x3; - case SHULKER_BOX: - return Containers.SHULKER_BOX; - case BLAST_FURNACE: - return Containers.BLAST_FURNACE; - case LECTERN: - return Containers.LECTERN; - case SMOKER: - return Containers.SMOKER; - case LOOM: - return Containers.LOOM; - case CARTOGRAPHY: - return Containers.CARTOGRAPHY_TABLE; - case GRINDSTONE: - return Containers.GRINDSTONE; - case STONECUTTER: - return Containers.STONECUTTER; - case SMITHING: - case SMITHING_NEW: - return Containers.SMITHING; - case CREATIVE: - case CRAFTING: - case MERCHANT: - throw new IllegalArgumentException("Can't open a " + inventory.getType() + " inventory!"); - case CRAFTER: - return Containers.CRAFTER_3x3; default: - // TODO: If it reaches the default case, should we throw an error? - return Containers.GENERIC_9x3; + final MenuType menu = type.getMenuType(); + if (menu == null) { + return Containers.GENERIC_9x3; + } else { + return ((CraftMenuType) menu).getHandle(); + } } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java new file mode 100644 index 0000000000..52c9833567 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java @@ -0,0 +1,89 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Preconditions; +import com.google.common.base.Suppliers; +import java.util.function.Supplier; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.level.EntityPlayer; +import net.minecraft.world.inventory.Container; +import net.minecraft.world.inventory.Containers; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.inventory.util.CraftMenus; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.MenuType; + +public class CraftMenuType implements MenuType.Typed, Handleable> { + + private final NamespacedKey key; + private final Containers handle; + private final Supplier> typeData; + + public CraftMenuType(NamespacedKey key, Containers handle) { + this.key = key; + this.handle = handle; + this.typeData = Suppliers.memoize(() -> CraftMenus.getMenuTypeData(this)); + } + + @Override + public Containers getHandle() { + return this.handle; + } + + @Override + public V create(final HumanEntity player, final String title) { + 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 EntityPlayer, "The given player must be an EntityPlayer"); + final EntityPlayer serverPlayer = (EntityPlayer) craftHuman.getHandle(); + + final Container container = typeData.get().menuBuilder().build(serverPlayer, this.handle); + container.setTitle(CraftChatMessage.fromString(title)[0]); + container.checkReachable = false; + return (V) container.getBukkitView(); + } + + @Override + public Typed typed() { + return this.typed(InventoryView.class); + } + + @Override + public Typed typed(Class clazz) { + if (clazz.isAssignableFrom(typeData.get().viewClass())) { + return (Typed) this; + } + + throw new IllegalArgumentException("Cannot type InventoryView " + this.key.toString() + " to InventoryView type " + clazz.getSimpleName()); + } + + @Override + public Class getInventoryViewClass() { + return typeData.get().viewClass(); + } + + @Override + public NamespacedKey getKey() { + return this.key; + } + + public static Containers bukkitToMinecraft(MenuType bukkit) { + return CraftRegistry.bukkitToMinecraft(bukkit); + } + + public static MenuType minecraftToBukkit(Containers minecraft) { + return CraftRegistry.minecraftToBukkit(minecraft, Registries.MENU, Registry.MENU); + } + + public static MenuType minecraftHolderToBukkit(Holder> minecraft) { + return minecraftToBukkit(minecraft.value()); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenuBuilder.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenuBuilder.java new file mode 100644 index 0000000000..dd4880b16a --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenuBuilder.java @@ -0,0 +1,38 @@ +package org.bukkit.craftbukkit.inventory.util; + +import net.minecraft.core.BlockPosition; +import net.minecraft.server.level.EntityPlayer; +import net.minecraft.world.ITileInventory; +import net.minecraft.world.entity.player.PlayerInventory; +import net.minecraft.world.inventory.Container; +import net.minecraft.world.inventory.ContainerAccess; +import net.minecraft.world.inventory.Containers; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.IBlockData; + +public interface CraftMenuBuilder { + + Container build(EntityPlayer player, Containers type); + + static CraftMenuBuilder worldAccess(LocationBoundContainerBuilder builder) { + return (EntityPlayer player, Containers type) -> { + return builder.build(player.nextContainerCounter(), player.getInventory(), ContainerAccess.create(player.level(), player.blockPosition())); + }; + } + + static CraftMenuBuilder tileEntity(TileEntityObjectBuilder objectBuilder, Block block) { + return (EntityPlayer player, Containers type) -> { + return objectBuilder.build(player.blockPosition(), block.defaultBlockState()).createMenu(player.nextContainerCounter(), player.getInventory(), player); + }; + } + + interface TileEntityObjectBuilder { + + ITileInventory build(BlockPosition blockPosition, IBlockData blockData); + } + + interface LocationBoundContainerBuilder { + + Container build(int syncId, PlayerInventory inventory, ContainerAccess access); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java new file mode 100644 index 0000000000..e63d3f3e50 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftMenus.java @@ -0,0 +1,115 @@ +package org.bukkit.craftbukkit.inventory.util; + +import static org.bukkit.craftbukkit.inventory.util.CraftMenuBuilder.*; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.world.TileInventory; +import net.minecraft.world.inventory.ContainerAnvil; +import net.minecraft.world.inventory.ContainerCartography; +import net.minecraft.world.inventory.ContainerEnchantTable; +import net.minecraft.world.inventory.ContainerGrindstone; +import net.minecraft.world.inventory.ContainerSmithing; +import net.minecraft.world.inventory.ContainerStonecutter; +import net.minecraft.world.inventory.ContainerWorkbench; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.CrafterBlockEntity; +import net.minecraft.world.level.block.entity.TileEntityBeacon; +import net.minecraft.world.level.block.entity.TileEntityBlastFurnace; +import net.minecraft.world.level.block.entity.TileEntityBrewingStand; +import net.minecraft.world.level.block.entity.TileEntityDispenser; +import net.minecraft.world.level.block.entity.TileEntityFurnaceFurnace; +import net.minecraft.world.level.block.entity.TileEntityHopper; +import net.minecraft.world.level.block.entity.TileEntityLectern; +import net.minecraft.world.level.block.entity.TileEntitySmoker; +import org.bukkit.craftbukkit.inventory.CraftMenuType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.MenuType; +import org.bukkit.inventory.view.AnvilView; +import org.bukkit.inventory.view.BeaconView; +import org.bukkit.inventory.view.BrewingStandView; +import org.bukkit.inventory.view.CrafterView; +import org.bukkit.inventory.view.EnchantmentView; +import org.bukkit.inventory.view.FurnaceView; +import org.bukkit.inventory.view.LecternView; +import org.bukkit.inventory.view.LoomView; +import org.bukkit.inventory.view.MerchantView; +import org.bukkit.inventory.view.StonecutterView; + +public final class CraftMenus { + + public record MenuTypeData(Class viewClass, CraftMenuBuilder menuBuilder) { + } + + private static final CraftMenuBuilder STANDARD = (player, menuType) -> menuType.create(player.nextContainerCounter(), player.getInventory()); + + public static MenuTypeData getMenuTypeData(CraftMenuType menuType) { + // 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 asType(new MenuTypeData<>(InventoryView.class, tileEntity(TileEntityDispenser::new, Blocks.DISPENSER))); + } + if (menuType == MenuType.CRAFTER_3X3) { + return asType(new MenuTypeData<>(CrafterView.class, tileEntity(CrafterBlockEntity::new, Blocks.CRAFTER))); + } + if (menuType == MenuType.ANVIL) { + return asType(new MenuTypeData<>(AnvilView.class, worldAccess(ContainerAnvil::new))); + } + if (menuType == MenuType.BEACON) { + return asType(new MenuTypeData<>(BeaconView.class, tileEntity(TileEntityBeacon::new, Blocks.BEACON))); + } + if (menuType == MenuType.BLAST_FURNACE) { + return asType(new MenuTypeData<>(FurnaceView.class, tileEntity(TileEntityBlastFurnace::new, Blocks.BLAST_FURNACE))); + } + if (menuType == MenuType.BREWING_STAND) { + return asType(new MenuTypeData<>(BrewingStandView.class, tileEntity(TileEntityBrewingStand::new, Blocks.BREWING_STAND))); + } + if (menuType == MenuType.CRAFTING) { + return asType(new MenuTypeData<>(InventoryView.class, worldAccess(ContainerWorkbench::new))); + } + if (menuType == MenuType.ENCHANTMENT) { + return asType(new MenuTypeData<>(EnchantmentView.class, (player, type) -> { + return new TileInventory((syncId, inventory, human) -> { + return worldAccess(ContainerEnchantTable::new).build(player, type); + }, IChatBaseComponent.empty()).createMenu(player.nextContainerCounter(), player.getInventory(), player); + })); + } + if (menuType == MenuType.FURNACE) { + return asType(new MenuTypeData<>(FurnaceView.class, tileEntity(TileEntityFurnaceFurnace::new, Blocks.FURNACE))); + } + if (menuType == MenuType.GRINDSTONE) { + return asType(new MenuTypeData<>(InventoryView.class, worldAccess(ContainerGrindstone::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 asType(new MenuTypeData<>(InventoryView.class, tileEntity(TileEntityHopper::new, Blocks.HOPPER))); + } + // 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 asType(new MenuTypeData<>(LecternView.class, tileEntity(TileEntityLectern::new, Blocks.LECTERN))); + } + if (menuType == MenuType.LOOM) { + return asType(new MenuTypeData<>(LoomView.class, STANDARD)); + } + if (menuType == MenuType.MERCHANT) { + return asType(new MenuTypeData<>(MerchantView.class, STANDARD)); + } + if (menuType == MenuType.SMITHING) { + return asType(new MenuTypeData<>(InventoryView.class, worldAccess(ContainerSmithing::new))); + } + if (menuType == MenuType.SMOKER) { + return asType(new MenuTypeData<>(FurnaceView.class, tileEntity(TileEntitySmoker::new, Blocks.SMOKER))); + } + if (menuType == MenuType.CARTOGRAPHY_TABLE) { + return asType(new MenuTypeData<>(InventoryView.class, worldAccess(ContainerCartography::new))); + } + if (menuType == MenuType.STONECUTTER) { + return asType(new MenuTypeData<>(StonecutterView.class, worldAccess(ContainerStonecutter::new))); + } + + return asType(new MenuTypeData<>(InventoryView.class, STANDARD)); + } + + private static MenuTypeData asType(MenuTypeData data) { + return (MenuTypeData) data; + } +} diff --git a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java index 4423e278d6..8579c6de27 100644 --- a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java +++ b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java @@ -11,6 +11,7 @@ import net.minecraft.world.entity.animal.FrogVariant; import net.minecraft.world.entity.animal.WolfVariant; import net.minecraft.world.entity.npc.VillagerProfession; import net.minecraft.world.entity.npc.VillagerType; +import net.minecraft.world.inventory.Containers; import net.minecraft.world.item.Instrument; import net.minecraft.world.level.block.entity.EnumBannerPatternType; import net.minecraft.world.level.saveddata.maps.MapDecorationType; @@ -33,6 +34,7 @@ import org.bukkit.craftbukkit.entity.CraftWolf; import org.bukkit.craftbukkit.generator.structure.CraftStructure; import org.bukkit.craftbukkit.generator.structure.CraftStructureType; import org.bukkit.craftbukkit.inventory.CraftItemType; +import org.bukkit.craftbukkit.inventory.CraftMenuType; import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; import org.bukkit.craftbukkit.map.CraftMapCursor; @@ -46,6 +48,7 @@ import org.bukkit.entity.Wolf; import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.StructureType; import org.bukkit.inventory.ItemType; +import org.bukkit.inventory.MenuType; import org.bukkit.inventory.meta.trim.TrimMaterial; import org.bukkit.inventory.meta.trim.TrimPattern; import org.bukkit.map.MapCursor; @@ -63,6 +66,7 @@ public class RegistriesArgumentProvider implements ArgumentsProvider { register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); register(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); register(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); + register(MenuType.class, Registries.MENU, CraftMenuType.class, Containers.class); register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffectList.class); register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class);