BY_NAME = Maps.newHashMap();
private final int maxStack;
private final short durability;
public final Class> data;
private final boolean legacy;
private final NamespacedKey key;
private Material(final int id) {
this(id, 64);
}
private Material(final int id, final int stack) {
this(id, stack, MaterialData.class);
}
private Material(final int id, final int stack, final int durability) {
this(id, stack, durability, MaterialData.class);
}
private Material(final int id, /*@NotNull*/ final Class> data) {
this(id, 64, data);
}
private Material(final int id, final int stack, /*@NotNull*/ final Class> data) {
this(id, stack, 0, data);
}
private Material(final int id, final int stack, final int durability, /*@NotNull*/ final Class> data) {
this.id = id;
this.durability = (short) durability;
this.maxStack = stack;
this.data = data;
this.legacy = this.name().startsWith(LEGACY_PREFIX);
this.key = NamespacedKey.minecraft(this.name().toLowerCase(Locale.ROOT));
// try to cache the constructor for this material
try {
if (MaterialData.class.isAssignableFrom(data)) {
this.ctor = (Constructor extends MaterialData>) data.getConstructor(Material.class, byte.class);
} else {
this.ctor = null;
}
} catch (NoSuchMethodException ex) {
throw new AssertionError(ex);
} catch (SecurityException ex) {
throw new AssertionError(ex);
}
}
/**
* Do not use for any reason.
*
* @return ID of this material
* @deprecated Magic value
*/
@Deprecated
public int getId() {
Preconditions.checkArgument(legacy, "Cannot get ID of Modern Material");
return id;
}
/**
* Do not use for any reason.
*
* @return legacy status
*/
@Deprecated
public boolean isLegacy() {
return legacy;
}
@NotNull
@Override
public NamespacedKey getKey() {
Preconditions.checkArgument(!legacy, "Cannot get key of Legacy Material");
return key;
}
/**
* Gets the maximum amount of this material that can be held in a stack.
*
* Note that this is the default maximum size for this Material.
* {@link ItemStack ItemStacks} are able to change their maximum stack size per
* stack with {@link ItemMeta#setMaxStackSize(Integer)}. If an ItemStack instance
* is available, {@link ItemStack#getMaxStackSize()} may be preferred.
*
* @return Maximum stack size for this material
*/
public int getMaxStackSize() {
return maxStack;
}
/**
* Gets the maximum durability of this material
*
* @return Maximum durability for this material
*/
public short getMaxDurability() {
return durability;
}
/**
* Creates a new {@link BlockData} instance for this Material, with all
* properties initialized to unspecified defaults.
*
* @return new data instance
*/
@NotNull
public BlockData createBlockData() {
return Bukkit.createBlockData(this);
}
/**
* Creates a new {@link BlockData} instance for this Material, with
* all properties initialized to unspecified defaults.
*
* @param consumer consumer to run on new instance before returning
* @return new data instance
*/
@NotNull
public BlockData createBlockData(@Nullable Consumer super BlockData> consumer) {
return Bukkit.createBlockData(this, consumer);
}
/**
* Creates a new {@link BlockData} instance for this Material, with all
* properties initialized to unspecified defaults, except for those provided
* in data.
*
* @param data data string
* @return new data instance
* @throws IllegalArgumentException if the specified data is not valid
*/
@NotNull
public BlockData createBlockData(@Nullable String data) throws IllegalArgumentException {
return Bukkit.createBlockData(this, data);
}
/**
* Gets the MaterialData class associated with this Material
*
* @return MaterialData associated with this Material
*/
@NotNull
public Class extends MaterialData> getData() {
Preconditions.checkArgument(legacy, "Cannot get data class of Modern Material");
return ctor.getDeclaringClass();
}
/**
* Constructs a new MaterialData relevant for this Material, with the
* given initial data
*
* @param raw Initial data to construct the MaterialData with
* @return New MaterialData with the given data
* @deprecated Magic value
*/
@Deprecated
@NotNull
public MaterialData getNewData(final byte raw) {
Preconditions.checkArgument(legacy, "Cannot get new data of Modern Material");
try {
return ctor.newInstance(this, raw);
} catch (InstantiationException ex) {
final Throwable t = ex.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Error) {
throw (Error) t;
}
throw new AssertionError(t);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
/**
* Checks if this Material is a placable block
*
* @return true if this material is a block
*/
public boolean isBlock() {
return asBlockType() != null;
}
/**
* Checks if this Material is edible.
*
* @return true if this Material is edible.
*/
public boolean isEdible() {
ItemType type = asItemType();
return type == null ? false : type.isEdible();
}
/**
* Attempts to get the Material with the given name.
*
* This is a normal lookup, names must be the precise name they are given
* in the enum.
*
* @param name Name of the material to get
* @return Material if found, or null
*/
@Nullable
public static Material getMaterial(@NotNull final String name) {
return getMaterial(name, false);
}
/**
* Attempts to get the Material with the given name.
*
* This is a normal lookup, names must be the precise name they are given in
* the enum (but optionally including the LEGACY_PREFIX if legacyName is
* true).
*
* If legacyName is true, then the lookup will be against legacy materials,
* but the returned Material will be a modern material (ie this method is
* useful for updating stored data).
*
* @param name Name of the material to get
* @param legacyName whether this is a legacy name lookup
* @return Material if found, or null
*/
@Nullable
public static Material getMaterial(@NotNull String name, boolean legacyName) {
if (legacyName) {
if (!name.startsWith(LEGACY_PREFIX)) {
name = LEGACY_PREFIX + name;
}
Material match = BY_NAME.get(name);
return Bukkit.getUnsafe().fromLegacy(match);
}
return BY_NAME.get(name);
}
/**
* Attempts to match the Material with the given name.
*
* This is a match lookup; names will be stripped of the "minecraft:"
* namespace, converted to uppercase, then stripped of special characters in
* an attempt to format it like the enum.
*
* @param name Name of the material to get
* @return Material if found, or null
*/
@Nullable
public static Material matchMaterial(@NotNull final String name) {
return matchMaterial(name, false);
}
/**
* Attempts to match the Material with the given name.
*
* This is a match lookup; names will be stripped of the "minecraft:"
* namespace, converted to uppercase, then stripped of special characters in
* an attempt to format it like the enum.
*
* @param name Name of the material to get
* @param legacyName whether this is a legacy name (see
* {@link #getMaterial(java.lang.String, boolean)}
* @return Material if found, or null
*/
@Nullable
public static Material matchMaterial(@NotNull final String name, boolean legacyName) {
Preconditions.checkArgument(name != null, "Name cannot be null");
String filtered = name;
if (filtered.startsWith(NamespacedKey.MINECRAFT + ":")) {
filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length());
}
filtered = filtered.toUpperCase(Locale.ROOT);
filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", "");
return getMaterial(filtered, legacyName);
}
static {
for (Material material : values()) {
BY_NAME.put(material.name(), material);
}
}
/**
* @return True if this material represents a playable music disk.
*/
public boolean isRecord() {
ItemType type = asItemType();
return type != null && type.isRecord();
}
/**
* Check if the material is a block and solid (can be built upon)
*
* @return True if this material is a block and solid
*/
public boolean isSolid() {
BlockType type = asBlockType();
return type != null && type.isSolid();
}
/**
* Check if the material is an air block.
*
* @return True if this material is an air block.
*/
public boolean isAir() {
BlockType type = asBlockType();
return type != null && type.isAir();
}
/**
* Check if the material is a block and does not block any light
*
* @return True if this material is a block and does not block any light
* @deprecated currently does not have an implementation which is well
* linked to the underlying server. Contributions welcome.
*/
@Deprecated
public boolean isTransparent() {
if (!isBlock()) {
return false;
}
switch (this) {
//
case ACACIA_BUTTON:
case ACACIA_SAPLING:
case ACTIVATOR_RAIL:
case AIR:
case ALLIUM:
case ATTACHED_MELON_STEM:
case ATTACHED_PUMPKIN_STEM:
case AZURE_BLUET:
case BARRIER:
case BEETROOTS:
case BIRCH_BUTTON:
case BIRCH_SAPLING:
case BLACK_CARPET:
case BLUE_CARPET:
case BLUE_ORCHID:
case BROWN_CARPET:
case BROWN_MUSHROOM:
case CARROTS:
case CAVE_AIR:
case CHORUS_FLOWER:
case CHORUS_PLANT:
case COCOA:
case COMPARATOR:
case CREEPER_HEAD:
case CREEPER_WALL_HEAD:
case CYAN_CARPET:
case DANDELION:
case DARK_OAK_BUTTON:
case DARK_OAK_SAPLING:
case DEAD_BUSH:
case DETECTOR_RAIL:
case DRAGON_HEAD:
case DRAGON_WALL_HEAD:
case END_GATEWAY:
case END_PORTAL:
case END_ROD:
case FERN:
case FIRE:
case FLOWER_POT:
case GRAY_CARPET:
case GREEN_CARPET:
case JUNGLE_BUTTON:
case JUNGLE_SAPLING:
case LADDER:
case LARGE_FERN:
case LEVER:
case LIGHT_BLUE_CARPET:
case LIGHT_GRAY_CARPET:
case LILAC:
case LILY_PAD:
case LIME_CARPET:
case MAGENTA_CARPET:
case MELON_STEM:
case NETHER_PORTAL:
case NETHER_WART:
case OAK_BUTTON:
case OAK_SAPLING:
case ORANGE_CARPET:
case ORANGE_TULIP:
case OXEYE_DAISY:
case PEONY:
case PINK_CARPET:
case PINK_TULIP:
case PLAYER_HEAD:
case PLAYER_WALL_HEAD:
case POPPY:
case POTATOES:
case POTTED_ACACIA_SAPLING:
case POTTED_ALLIUM:
case POTTED_AZALEA_BUSH:
case POTTED_AZURE_BLUET:
case POTTED_BIRCH_SAPLING:
case POTTED_BLUE_ORCHID:
case POTTED_BROWN_MUSHROOM:
case POTTED_CACTUS:
case POTTED_DANDELION:
case POTTED_DARK_OAK_SAPLING:
case POTTED_DEAD_BUSH:
case POTTED_FERN:
case POTTED_FLOWERING_AZALEA_BUSH:
case POTTED_JUNGLE_SAPLING:
case POTTED_OAK_SAPLING:
case POTTED_ORANGE_TULIP:
case POTTED_OXEYE_DAISY:
case POTTED_PINK_TULIP:
case POTTED_POPPY:
case POTTED_RED_MUSHROOM:
case POTTED_RED_TULIP:
case POTTED_SPRUCE_SAPLING:
case POTTED_WHITE_TULIP:
case POWERED_RAIL:
case PUMPKIN_STEM:
case PURPLE_CARPET:
case RAIL:
case REDSTONE_TORCH:
case REDSTONE_WALL_TORCH:
case REDSTONE_WIRE:
case RED_CARPET:
case RED_MUSHROOM:
case RED_TULIP:
case REPEATER:
case ROSE_BUSH:
case SHORT_GRASS:
case SKELETON_SKULL:
case SKELETON_WALL_SKULL:
case SNOW:
case SPRUCE_BUTTON:
case SPRUCE_SAPLING:
case STONE_BUTTON:
case STRUCTURE_VOID:
case SUGAR_CANE:
case SUNFLOWER:
case TALL_GRASS:
case TORCH:
case TRIPWIRE:
case TRIPWIRE_HOOK:
case VINE:
case VOID_AIR:
case WALL_TORCH:
case WHEAT:
case WHITE_CARPET:
case WHITE_TULIP:
case WITHER_SKELETON_SKULL:
case WITHER_SKELETON_WALL_SKULL:
case YELLOW_CARPET:
case ZOMBIE_HEAD:
case ZOMBIE_WALL_HEAD:
// ----- Legacy Separator -----
case LEGACY_AIR:
case LEGACY_SAPLING:
case LEGACY_POWERED_RAIL:
case LEGACY_DETECTOR_RAIL:
case LEGACY_LONG_GRASS:
case LEGACY_DEAD_BUSH:
case LEGACY_YELLOW_FLOWER:
case LEGACY_RED_ROSE:
case LEGACY_BROWN_MUSHROOM:
case LEGACY_RED_MUSHROOM:
case LEGACY_TORCH:
case LEGACY_FIRE:
case LEGACY_REDSTONE_WIRE:
case LEGACY_CROPS:
case LEGACY_LADDER:
case LEGACY_RAILS:
case LEGACY_LEVER:
case LEGACY_REDSTONE_TORCH_OFF:
case LEGACY_REDSTONE_TORCH_ON:
case LEGACY_STONE_BUTTON:
case LEGACY_SNOW:
case LEGACY_SUGAR_CANE_BLOCK:
case LEGACY_PORTAL:
case LEGACY_DIODE_BLOCK_OFF:
case LEGACY_DIODE_BLOCK_ON:
case LEGACY_PUMPKIN_STEM:
case LEGACY_MELON_STEM:
case LEGACY_VINE:
case LEGACY_WATER_LILY:
case LEGACY_NETHER_WARTS:
case LEGACY_ENDER_PORTAL:
case LEGACY_COCOA:
case LEGACY_TRIPWIRE_HOOK:
case LEGACY_TRIPWIRE:
case LEGACY_FLOWER_POT:
case LEGACY_CARROT:
case LEGACY_POTATO:
case LEGACY_WOOD_BUTTON:
case LEGACY_SKULL:
case LEGACY_REDSTONE_COMPARATOR_OFF:
case LEGACY_REDSTONE_COMPARATOR_ON:
case LEGACY_ACTIVATOR_RAIL:
case LEGACY_CARPET:
case LEGACY_DOUBLE_PLANT:
case LEGACY_END_ROD:
case LEGACY_CHORUS_PLANT:
case LEGACY_CHORUS_FLOWER:
case LEGACY_BEETROOT_BLOCK:
case LEGACY_END_GATEWAY:
case LEGACY_STRUCTURE_VOID:
//
return true;
default:
return false;
}
}
/**
* Check if the material is a block and can catch fire
*
* @return True if this material is a block and can catch fire
*/
public boolean isFlammable() {
BlockType type = asBlockType();
return type != null && type.isFlammable();
}
/**
* Check if the material is a block and can burn away
*
* @return True if this material is a block and can burn away
*/
public boolean isBurnable() {
BlockType type = asBlockType();
return type != null && type.isBurnable();
}
/**
* Checks if this Material can be used as fuel in a Furnace
*
* @return true if this Material can be used as fuel.
*/
public boolean isFuel() {
ItemType type = asItemType();
return type != null && type.isFuel();
}
/**
* Check if the material is a block and occludes light in the lighting engine.
*
* Generally speaking, most full blocks will occlude light. Non-full blocks are
* not occluding (e.g. anvils, chests, tall grass, stairs, etc.), nor are specific
* full blocks such as barriers or spawners which block light despite their texture.
*
* An occluding block will have the following effects:
*
* - Chests cannot be opened if an occluding block is above it.
*
- Mobs cannot spawn inside of occluding blocks.
*
- Only occluding blocks can be "powered" ({@link Block#isBlockPowered()}).
*
* This list may be inconclusive. For a full list of the side effects of an occluding
* block, see the Minecraft Wiki.
*
* @return True if this material is a block and occludes light
*/
public boolean isOccluding() {
BlockType type = asBlockType();
return type != null && type.isOccluding();
}
/**
* @return True if this material is affected by gravity.
*/
public boolean hasGravity() {
BlockType type = asBlockType();
return type != null && type.hasGravity();
}
/**
* Checks if this Material is an obtainable item.
*
* @return true if this material is an item
*/
public boolean isItem() {
return asItemType() != null;
}
/**
* Checks if this Material can be interacted with.
*
* Interactable materials include those with functionality when they are
* interacted with by a player such as chests, furnaces, etc.
*
* Some blocks such as piston heads and stairs are considered interactable
* though may not perform any additional functionality.
*
* Note that the interactability of some materials may be dependant on their
* state as well. This method will return true if there is at least one
* state in which additional interact handling is performed for the
* material.
*
* @return true if this material can be interacted with.
*/
public boolean isInteractable() {
BlockType type = asBlockType();
return type != null && type.isInteractable();
}
/**
* Obtains the block's hardness level (also known as "strength").
*
* This number is used to calculate the time required to break each block.
*
* Only available when {@link #isBlock()} is true.
*
* @return the hardness of that material.
*/
public float getHardness() {
BlockType type = asBlockType();
Preconditions.checkArgument(type != null, "The Material is not a block!");
return type.getHardness();
}
/**
* Obtains the blast resistance value (also known as block "durability").
*
* This value is used in explosions to calculate whether a block should be
* broken or not.
*
* Only available when {@link #isBlock()} is true.
*
* @return the blast resistance of that material.
*/
public float getBlastResistance() {
BlockType type = asBlockType();
Preconditions.checkArgument(type != null, "The Material is not a block!");
return type.getBlastResistance();
}
/**
* Returns a value that represents how 'slippery' the block is.
*
* Blocks with higher slipperiness, like {@link Material#ICE} can be slid on
* further by the player and other entities.
*
* Most blocks have a default slipperiness of {@code 0.6f}.
*
* Only available when {@link #isBlock()} is true.
*
* @return the slipperiness of this block
*/
public float getSlipperiness() {
BlockType type = asBlockType();
Preconditions.checkArgument(type != null, "The Material is not a block!");
return type.getSlipperiness();
}
/**
* Determines the remaining item in a crafting grid after crafting with this
* ingredient.
*
* Only available when {@link #isItem()} is true.
*
* @return the item left behind when crafting, or null if nothing is.
*/
@Nullable
public Material getCraftingRemainingItem() {
ItemType type = asItemType();
Preconditions.checkArgument(type != null, "The Material is not an item!");
return type.getCraftingRemainingItem() == null ? null : type.getCraftingRemainingItem().asMaterial();
}
/**
* Get the best suitable slot for this Material.
*
* For most items this will be {@link EquipmentSlot#HAND}.
*
* @return the best EquipmentSlot for this Material
*/
@NotNull
public EquipmentSlot getEquipmentSlot() {
Preconditions.checkArgument(isItem(), "The Material is not an item!");
switch (this) {
//
case CARVED_PUMPKIN:
case CHAINMAIL_HELMET:
case CREEPER_HEAD:
case DIAMOND_HELMET:
case DRAGON_HEAD:
case GOLDEN_HELMET:
case IRON_HELMET:
case LEATHER_HELMET:
case NETHERITE_HELMET:
case PLAYER_HEAD:
case PIGLIN_HEAD:
case SKELETON_SKULL:
case TURTLE_HELMET:
case WITHER_SKELETON_SKULL:
case ZOMBIE_HEAD:
return EquipmentSlot.HEAD;
case CHAINMAIL_CHESTPLATE:
case DIAMOND_CHESTPLATE:
case ELYTRA:
case GOLDEN_CHESTPLATE:
case IRON_CHESTPLATE:
case LEATHER_CHESTPLATE:
case NETHERITE_CHESTPLATE:
return EquipmentSlot.CHEST;
case CHAINMAIL_LEGGINGS:
case DIAMOND_LEGGINGS:
case GOLDEN_LEGGINGS:
case IRON_LEGGINGS:
case LEATHER_LEGGINGS:
case NETHERITE_LEGGINGS:
return EquipmentSlot.LEGS;
case CHAINMAIL_BOOTS:
case DIAMOND_BOOTS:
case GOLDEN_BOOTS:
case IRON_BOOTS:
case LEATHER_BOOTS:
case NETHERITE_BOOTS:
return EquipmentSlot.FEET;
case SHIELD:
return EquipmentSlot.OFF_HAND;
case BLACK_CARPET:
case BLUE_CARPET:
case BROWN_CARPET:
case CYAN_CARPET:
case DIAMOND_HORSE_ARMOR:
case GOLDEN_HORSE_ARMOR:
case GRAY_CARPET:
case GREEN_CARPET:
case IRON_HORSE_ARMOR:
case LEATHER_HORSE_ARMOR:
case LIGHT_BLUE_CARPET:
case LIGHT_GRAY_CARPET:
case LIME_CARPET:
case MAGENTA_CARPET:
case ORANGE_CARPET:
case PINK_CARPET:
case PURPLE_CARPET:
case RED_CARPET:
case WHITE_CARPET:
case WOLF_ARMOR:
case YELLOW_CARPET:
return EquipmentSlot.BODY;
default:
return EquipmentSlot.HAND;
//
}
}
/**
* Return an immutable copy of all default {@link Attribute}s and their
* {@link AttributeModifier}s for a given {@link EquipmentSlot}.
*
* Default attributes are those that are always preset on some items, such
* as the attack damage on weapons or the armor value on armor.
*
* Only available when {@link #isItem()} is true.
*
* @param slot the {@link EquipmentSlot} to check
* @return the immutable {@link Multimap} with the respective default
* Attributes and modifiers, or an empty map if no attributes are set.
*/
@NotNull
public Multimap getDefaultAttributeModifiers(@NotNull EquipmentSlot slot) {
ItemType type = asItemType();
Preconditions.checkArgument(type != null, "The Material is not an item!");
return type.getDefaultAttributeModifiers(slot);
}
/**
* Get the {@link CreativeCategory} to which this material belongs.
*
* @return the creative category. null if does not belong to a category
*/
@Nullable
public CreativeCategory getCreativeCategory() {
ItemType type = asItemType();
return type == null ? null : type.getCreativeCategory();
}
/**
* Get the translation key of the item or block associated with this
* material.
*
* If this material has both an item and a block form, the item form is
* used.
*
* @return the translation key of the item or block associated with this
* material
* @see #getBlockTranslationKey()
* @see #getItemTranslationKey()
*/
@Override
@NotNull
public String getTranslationKey() {
if (this.isItem()) {
return asItemType().getTranslationKey();
} else {
return asBlockType().getTranslationKey();
}
}
/**
* Get the translation key of the block associated with this material, or
* null if this material does not have an associated block.
*
* @return the translation key of the block associated with this material,
* or null if this material does not have an associated block
*/
@Nullable
public String getBlockTranslationKey() {
BlockType type = asBlockType();
return type == null ? null : type.getTranslationKey();
}
/**
* Get the translation key of the item associated with this material, or
* null if this material does not have an associated item.
*
* @return the translation key of the item associated with this material, or
* null if this material does not have an associated item.
*/
@Nullable
public String getItemTranslationKey() {
ItemType type = asItemType();
return type == null ? null : type.getTranslationKey();
}
/**
* Gets if the Material is enabled by the features in a world.
*
* @param world the world to check
* @return true if this material can be used in this World.
*/
public boolean isEnabledByFeature(@NotNull World world) {
if (isItem()) {
return Bukkit.getDataPackManager().isEnabledByFeature(asItemType(), world);
}
return Bukkit.getDataPackManager().isEnabledByFeature(asBlockType(), world);
}
/**
* Checks whether this material is compostable (can be inserted into a
* composter).
*
* @return true if this material is compostable
* @see #getCompostChance()
*/
public boolean isCompostable() {
return isItem() && asItemType().isCompostable();
}
/**
* Get the chance that this material will successfully compost. The returned
* value is between 0 and 1 (inclusive).
*
* Materials with a compost chance of 1 will always raise the composter's
* level, while materials with a compost chance of 0 will never raise it.
*
* Plugins should check that {@link #isCompostable} returns true before
* calling this method.
*
* @return the chance that this material will successfully compost
* @throws IllegalArgumentException if the material is not compostable
* @see #isCompostable()
*/
public float getCompostChance() {
ItemType type = asItemType();
Preconditions.checkArgument(type != null, "The Material is not an item!");
return type.getCompostChance();
}
/**
* Tries to convert this Material to an item type
*
* @return the converted item type or null
* @apiNote only for internal use
*/
@ApiStatus.Internal
@Nullable
public ItemType asItemType() {
Material material = this;
if (isLegacy()) {
material = Bukkit.getUnsafe().fromLegacy(this);
}
return Registry.ITEM.get(material.key);
}
/**
* Tries to convert this Material to a block type
*
* @return the converted block type or null
* @apiNote only for internal use
*/
@ApiStatus.Internal
@Nullable
public BlockType asBlockType() {
Material material = this;
if (isLegacy()) {
material = Bukkit.getUnsafe().fromLegacy(this);
}
return Registry.BLOCK.get(material.key);
}
}