#1458: Add MenuType API

By: Miles Holder <mwholder2005@gmail.com>
This commit is contained in:
CraftBukkit/Spigot 2024-09-07 18:52:00 +10:00
parent ac5e815b3e
commit fbea3cdc0c
6 changed files with 260 additions and 46 deletions

View File

@ -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<B extends Keyed, M> implements Registry<B> {
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);
}

View File

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

View File

@ -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<V extends InventoryView> implements MenuType.Typed<V>, Handleable<Containers<?>> {
private final NamespacedKey key;
private final Containers<?> handle;
private final Supplier<CraftMenus.MenuTypeData<V>> 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<InventoryView> typed() {
return this.typed(InventoryView.class);
}
@Override
public <V extends InventoryView> Typed<V> typed(Class<V> clazz) {
if (clazz.isAssignableFrom(typeData.get().viewClass())) {
return (Typed<V>) this;
}
throw new IllegalArgumentException("Cannot type InventoryView " + this.key.toString() + " to InventoryView type " + clazz.getSimpleName());
}
@Override
public Class<? extends InventoryView> 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<Containers<?>> minecraft) {
return minecraftToBukkit(minecraft.value());
}
}

View File

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

View File

@ -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<V extends InventoryView>(Class<V> viewClass, CraftMenuBuilder menuBuilder) {
}
private static final CraftMenuBuilder STANDARD = (player, menuType) -> menuType.create(player.nextContainerCounter(), player.getInventory());
public static <V extends InventoryView> MenuTypeData<V> 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 <V extends InventoryView> MenuTypeData<V> asType(MenuTypeData<?> data) {
return (MenuTypeData<V>) data;
}
}

View File

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