diff --git a/.idea/artifacts/MCTPAudio_jar.xml b/.idea/artifacts/MCTPAudio_jar.xml index 5b631ee..7806cbd 100644 --- a/.idea/artifacts/MCTPAudio_jar.xml +++ b/.idea/artifacts/MCTPAudio_jar.xml @@ -3,8 +3,9 @@ $PROJECT_DIR$/out/artifacts/MCTPAudio_jar - + + \ No newline at end of file diff --git a/.idea/libraries/spigot_1_16_3.xml b/.idea/libraries/spigot_1_16_3.xml new file mode 100644 index 0000000..ff8b259 --- /dev/null +++ b/.idea/libraries/spigot_1_16_3.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MCTPAudio.iml b/MCTPAudio.iml index 6c4466f..590f193 100644 --- a/MCTPAudio.iml +++ b/MCTPAudio.iml @@ -16,8 +16,7 @@ - - + @@ -54,5 +53,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/me/mctp/Main.java b/src/me/mctp/Main.java index 22abd86..8f47f5b 100644 --- a/src/me/mctp/Main.java +++ b/src/me/mctp/Main.java @@ -1,12 +1,12 @@ package me.mctp; import me.mctp.commands.MCTPAudioCMD; -import me.mctp.commands.MCTPShowCMD; import me.mctp.listener.LogoutListener; import me.mctp.listener.WGListener; import me.mctp.managers.WGManager; import me.mctp.radio.Playlist; import me.mctp.socket.Client; +import nl.iobyte.menuapi.MenuAPI; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.event.Listener; @@ -39,18 +39,19 @@ public class Main extends JavaPlugin implements Listener { pl = this; - client = new Client("ws://144.91.100.169:30217"); + client = new Client("ws://bots.d-group.nl:8166"); client.connect(); getCommand("audio").setExecutor(new MCTPAudioCMD()); getCommand("mctpaudio").setExecutor(new MCTPAudioCMD()); - getCommand("mctpshow").setExecutor(new MCTPShowCMD()); Bukkit.getPluginManager().registerEvents(new WGListener(), this); Bukkit.getPluginManager().registerEvents(new LogoutListener(), this); playlist = new Playlist(); + MenuAPI.register(this); + Bukkit.getLogger().info(prefix + " __ __ ____ _____ ____ _ _ _ "); Bukkit.getLogger().info(prefix + " | \\/ |/ ___|_ _| _ \\ / \\ _ _ __| (_) ___ "); Bukkit.getLogger().info(prefix + " | |\\/| | | | | | |_) / _ \\| | | |/ _` | |/ _ \\ "); diff --git a/src/me/mctp/commands/MCTPShowCMD.java b/src/me/mctp/commands/MCTPShowCMD.java deleted file mode 100644 index fcb8f06..0000000 --- a/src/me/mctp/commands/MCTPShowCMD.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.mctp.commands; - -import me.mctp.Main; -import me.mctp.utils.Laser; -import org.bukkit.ChatColor; -import org.bukkit.Sound; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.HashMap; -import java.util.Map; - -public class MCTPShowCMD implements CommandExecutor { - public static String prefix = (ChatColor.GOLD + "[" + ChatColor.YELLOW + "MCTP" + ChatColor.GOLD + "] " + ChatColor.GRAY); - - private Map lasers =new HashMap<>(); - - @Override - public boolean onCommand(CommandSender sender, Command cmd, String commandlabel, String[] args) { - if (cmd.getName().equalsIgnoreCase("mctpshow")) { - if (!sender.hasPermission("mctp.show")) { - sender.sendMessage(prefix + "You don't have the permission to do this."); - return true; - } - - if (args.length == 1 && args[0].equalsIgnoreCase("demo")) { - Player p = (Player) sender; - - if (lasers.containsKey(p)){ - lasers.get(p).cancel(); - }else { - try{ - lasers.put(p, new LaserRunnable(p)); - lasers.get(p).runTaskTimer(Main.getPlugin(), 5, 1); - }catch (ReflectiveOperationException e){ - e.printStackTrace(); - } - } - } - } - return true; - } - - public class LaserRunnable extends BukkitRunnable { - public static final byte LOADING_TIME = 30; - public static final byte RANGE = 10; - - private final Laser laser; - private final Player p; - - public byte loading = 0; - - public LaserRunnable(Player p) throws ReflectiveOperationException{ - this.p = p; - this.laser = new Laser(p.getLocation(), p.getLocation().add(0, 1, 0), -1, 50); - this.laser.start(Main.getPlugin()); - } - - public void run(){ - if (loading != LOADING_TIME){ - loading++; - p.getWorld().playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.7f, loading == LOADING_TIME ? 1.5f : 0.2f); - } - try{ - laser.moveStart(p.getLocation().add(0, 0.8, 0)); - laser.moveEnd(p.getLocation().add(0, 1.2, 0).add(p.getLocation().getDirection().multiply(loading == LOADING_TIME ? RANGE : loading / (LOADING_TIME / RANGE * 1.3)))); - }catch (ReflectiveOperationException e){ - e.printStackTrace(); - } - } - - public synchronized void cancel() throws IllegalStateException{ - laser.stop(); - lasers.remove(p); - super.cancel(); - } - } -} diff --git a/src/me/mctp/utils/Laser.java b/src/me/mctp/utils/Laser.java deleted file mode 100644 index 75e4f1f..0000000 --- a/src/me/mctp/utils/Laser.java +++ /dev/null @@ -1,376 +0,0 @@ -package me.mctp.utils; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashSet; -import java.util.UUID; - -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; - -/** - * A whole class to create Guardian Beams by reflection
- * Inspired by the API GuardianBeamAPI
- * 1.9 -> 1.15 - * - * @see GitHub page - * @author SkytAsul - */ -public class Laser { - private final int duration; - private final int distanceSquared; - private Location start; - private Location end; - - private final Object createGuardianPacket; - private final Object createSquidPacket; - private final Object teamAddPacket; - private final Object destroyPacket; - private final Object metadataPacketGuardian; - private final Object metadataPacketSquid; - private final Object fakeGuardianDataWatcher; - - private final int squid; - private final UUID squidUUID; - private final int guardian; - private final UUID guardianUUID; - - private BukkitRunnable run; - private HashSet show = new HashSet<>(); - - /** - * Create a Laser instance - * @param start Location where laser will starts - * @param end Location where laser will ends - * @param duration Duration of laser in seconds (-1 if infinite) - * @param distance Distance where laser will be visible - */ - public Laser(Location start, Location end, int duration, int distance) throws ReflectiveOperationException { - this.start = start; - this.end = end; - this.duration = duration; - distanceSquared = distance * distance; - - createSquidPacket = Packets.createPacketSquidSpawn(end); - squid = (int) Packets.getField(Packets.packetSpawn, "a", createSquidPacket); - squidUUID = (UUID) Packets.getField(Packets.packetSpawn, "b", createSquidPacket); - metadataPacketSquid = Packets.createPacketMetadata(squid, Packets.fakeSquidWatcher); - Packets.setDirtyWatcher(Packets.fakeSquidWatcher); - - fakeGuardianDataWatcher = Packets.createFakeDataWatcher(); - createGuardianPacket = Packets.createPacketGuardianSpawn(start, fakeGuardianDataWatcher, squid); - guardian = (int) Packets.getField(Packets.packetSpawn, "a", createGuardianPacket); - guardianUUID = (UUID) Packets.getField(Packets.packetSpawn, "b", createGuardianPacket); - metadataPacketGuardian = Packets.createPacketMetadata(guardian, fakeGuardianDataWatcher); - - teamAddPacket = Packets.createPacketTeamAddEntities(squidUUID, guardianUUID); - destroyPacket = Packets.createPacketRemoveEntities(squid, guardian); - } - - public void start(Plugin plugin) { - Validate.isTrue(run == null, "Task already started"); - run = new BukkitRunnable() { - int time = duration; - - @Override - public void run() { - try { - if (time == 0) { - cancel(); - return; - } - for (Player p : start.getWorld().getPlayers()) { - if (isCloseEnough(p.getLocation())) { - if (!show.contains(p)) { - sendStartPackets(p); - show.add(p); - } - }else if (show.contains(p)) { - Packets.sendPacket(p, destroyPacket); - show.remove(p); - } - } - if (time != -1) time--; - }catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - } - - @Override - public synchronized void cancel() throws IllegalStateException { - super.cancel(); - try { - for (Player p : show) { - Packets.sendPacket(p, destroyPacket); - } - }catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - run = null; - } - }; - run.runTaskTimerAsynchronously(plugin, 0L, 20L); - } - - public void stop() { - Validate.isTrue(run != null, "Task not started"); - run.cancel(); - } - - public void moveStart(Location location) throws ReflectiveOperationException { - this.start = location; - Object packet = Packets.createPacketMoveEntity(start, guardian); - for (Player p : show) { - Packets.sendPacket(p, packet); - } - } - - public Location getStart() { - return start; - } - - public void moveEnd(Location location) throws ReflectiveOperationException { - this.end = location; - Object packet = Packets.createPacketMoveEntity(end, squid); - for (Player p : show) { - Packets.sendPacket(p, packet); - } - } - - public Location getEnd() { - return end; - } - - public void callColorChange() throws ReflectiveOperationException{ - for (Player p : show) { - Packets.sendPacket(p, metadataPacketGuardian); - } - } - - public boolean isStarted() { - return run != null; - } - - private void sendStartPackets(Player p) throws ReflectiveOperationException { - Packets.sendPacket(p, createSquidPacket); - Packets.sendPacket(p, createGuardianPacket); - if (Packets.version > 14) { - Packets.sendPacket(p, metadataPacketSquid); - Packets.sendPacket(p, metadataPacketGuardian); - } - Packets.sendPacket(p, Packets.packetTeamCreate); - Packets.sendPacket(p, teamAddPacket); - } - - private boolean isCloseEnough(Location location) { - return start.distanceSquared(location) <= distanceSquared || - end.distanceSquared(location) <= distanceSquared; - } - - - - private static class Packets { - private static int lastIssuedEID = 2000000000; - - static int generateEID() { - return lastIssuedEID++; - } - - private static int version = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3].substring(1).split("_")[1]); - private static String npack = "net.minecraft.server." + Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3] + "."; - private static String cpack = Bukkit.getServer().getClass().getPackage().getName() + "."; - private static Object packetTeamCreate; - private static Constructor watcherConstructor; - private static Method watcherSet; - private static Method watcherRegister; - private static Method watcherDirty; - private static Class packetSpawn; - private static Class packetRemove; - private static Class packetTeleport; - private static Class packetTeam; - private static Class packetMetadata; - private static Object watcherObject1; // invisilibity - private static Object watcherObject2; // spikes - private static Object watcherObject3; // attack id - private static int squidID; - private static int guardianID; - - private static Object fakeSquid; - private static Object fakeSquidWatcher; - - static { - try { - String watcherName1 = null, watcherName2 = null, watcherName3 = null; - if (version < 13) { - watcherName1 = "Z"; - watcherName2 = "bA"; - watcherName3 = "bB"; - squidID = 94; - guardianID = 68; - }else if (version == 13) { - watcherName1 = "ac"; - watcherName2 = "bF"; - watcherName3 = "bG"; - squidID = 70; - guardianID = 28; - }else if (version == 14) { - watcherName1 = "W"; - watcherName2 = "b"; - watcherName3 = "bD"; - squidID = 73; - guardianID = 30; - }else if (version > 14) { - watcherName1 = "T"; - watcherName2 = "b"; - watcherName3 = "bA"; - squidID = 74; - guardianID = 31; - } - watcherObject1 = getField(Class.forName(npack + "Entity"), watcherName1, null); - watcherObject2 = getField(Class.forName(npack + "EntityGuardian"), watcherName2, null); - watcherObject3 = getField(Class.forName(npack + "EntityGuardian"), watcherName3, null); - - watcherConstructor = Class.forName(npack + "DataWatcher").getDeclaredConstructor(Class.forName(npack + "Entity")); - watcherSet = getMethod(Class.forName(npack + "DataWatcher"), "set"); - watcherRegister = getMethod(Class.forName(npack + "DataWatcher"), "register"); - if (version >= 15) watcherDirty = getMethod(Class.forName(npack + "DataWatcher"), "markDirty"); - packetSpawn = Class.forName(npack + "PacketPlayOutSpawnEntityLiving"); - packetRemove = Class.forName(npack + "PacketPlayOutEntityDestroy"); - packetTeleport = Class.forName(npack + "PacketPlayOutEntityTeleport"); - packetTeam = Class.forName(npack + "PacketPlayOutScoreboardTeam"); - packetMetadata = Class.forName(npack + "PacketPlayOutEntityMetadata"); - - packetTeamCreate = packetTeam.newInstance(); - setField(packetTeamCreate, "a", "noclip"); - setField(packetTeamCreate, "i", 0); - setField(packetTeamCreate, "f", "never"); - - Object world = Class.forName(cpack + "CraftWorld").getDeclaredMethod("getHandle").invoke(Bukkit.getWorlds().get(0)); - Object[] entityConstructorParams = version < 14 ? new Object[] { world } : new Object[] { Class.forName(npack + "EntityTypes").getDeclaredField("SQUID").get(null), world }; - fakeSquid = getMethod(Class.forName(cpack + "entity.CraftSquid"), "getHandle").invoke(Class.forName(cpack + "entity.CraftSquid").getDeclaredConstructors()[0].newInstance( - null, Class.forName(npack + "EntitySquid").getDeclaredConstructors()[0].newInstance( - entityConstructorParams))); - fakeSquidWatcher = createFakeDataWatcher(); - tryWatcherSet(fakeSquidWatcher, watcherObject1, (byte) 32); - }catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - } - - public static void sendPacket(Player p, Object packet) throws ReflectiveOperationException { - Object entityPlayer = Class.forName(cpack + "entity.CraftPlayer").getDeclaredMethod("getHandle").invoke(p); - Object playerConnection = entityPlayer.getClass().getDeclaredField("playerConnection").get(entityPlayer); - playerConnection.getClass().getDeclaredMethod("sendPacket", Class.forName(npack + "Packet")).invoke(playerConnection, packet); - } - - public static Object createFakeDataWatcher() throws ReflectiveOperationException { - Object watcher = watcherConstructor.newInstance(fakeSquid); - if (version > 13) setField(watcher, "registrationLocked", false); - return watcher; - } - - public static void setDirtyWatcher(Object watcher) throws ReflectiveOperationException { - if (version >= 15) watcherDirty.invoke(watcher, watcherObject1); - } - - public static Object createPacketSquidSpawn(Location location) throws ReflectiveOperationException { - Object packet = packetSpawn.newInstance(); - setField(packet, "a", generateEID()); - setField(packet, "b", UUID.randomUUID()); - setField(packet, "c", squidID); - setField(packet, "d", location.getX()); - setField(packet, "e", location.getY()); - setField(packet, "f", location.getZ()); - setField(packet, "j", (byte) (location.getYaw() * 256.0F / 360.0F)); - setField(packet, "k", (byte) (location.getPitch() * 256.0F / 360.0F)); - if (version <= 14) setField(packet, "m", fakeSquidWatcher); - return packet; - } - - public static Object createPacketGuardianSpawn(Location location, Object watcher, int squidId) throws ReflectiveOperationException { - Object packet = packetSpawn.newInstance(); - setField(packet, "a", generateEID()); - setField(packet, "b", UUID.randomUUID()); - setField(packet, "c", guardianID); - setField(packet, "d", location.getX()); - setField(packet, "e", location.getY()); - setField(packet, "f", location.getZ()); - setField(packet, "j", (byte) (location.getYaw() * 256.0F / 360.0F)); - setField(packet, "k", (byte) (location.getPitch() * 256.0F / 360.0F)); - tryWatcherSet(watcher, watcherObject1, (byte) 32); - tryWatcherSet(watcher, watcherObject2, false); - tryWatcherSet(watcher, watcherObject3, squidId); - if (version <= 14) setField(packet, "m", watcher); - return packet; - } - - public static Object createPacketRemoveEntities(int squidId, int guardianId) throws ReflectiveOperationException { - Object packet = packetRemove.newInstance(); - setField(packet, "a", new int[] { squidId, guardianId }); - return packet; - } - - public static Object createPacketMoveEntity(Location location, int entityId) throws ReflectiveOperationException { - Object packet = packetTeleport.newInstance(); - setField(packet, "a", entityId); - setField(packet, "b", location.getX()); - setField(packet, "c", location.getY()); - setField(packet, "d", location.getZ()); - setField(packet, "e", (byte) (location.getYaw() * 256.0F / 360.0F)); - setField(packet, "f", (byte) (location.getPitch() * 256.0F / 360.0F)); - setField(packet, "g", true); - return packet; - } - - public static Object createPacketTeamAddEntities(UUID squidUUID, UUID guardianUUID) throws ReflectiveOperationException { - Object packet = packetTeam.newInstance(); - setField(packet, "a", "noclip"); - setField(packet, "i", 3); - Collection players = (Collection) getField(packetTeam, "h", packet); - players.add(squidUUID.toString()); - players.add(guardianUUID.toString()); - return packet; - } - - private static Object createPacketMetadata(int entityId, Object watcher) throws ReflectiveOperationException { - return packetMetadata.getConstructor(int.class, watcher.getClass(), boolean.class).newInstance(entityId, watcher, false); - } - - private static void tryWatcherSet(Object watcher, Object watcherObject, Object watcherData) throws ReflectiveOperationException { - try { - watcherSet.invoke(watcher, watcherObject, watcherData); - }catch (InvocationTargetException ex) { - watcherRegister.invoke(watcher, watcherObject, watcherData); - if (version >= 15) watcherDirty.invoke(watcher, watcherObject); - } - } - - /* Reflection utils */ - private static Method getMethod(Class clazz, String name) { - for (Method m : clazz.getDeclaredMethods()) { - if (m.getName().equals(name)) return m; - } - return null; - } - - private static void setField(Object instance, String name, Object value) throws ReflectiveOperationException { - Validate.notNull(instance); - Field field = instance.getClass().getDeclaredField(name); - field.setAccessible(true); - field.set(instance, value); - } - - private static Object getField(Class clazz, String name, Object instance) throws ReflectiveOperationException { - Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - return field.get(instance); - } - } -} \ No newline at end of file diff --git a/src/me/mctp/utils/MultiPageGUI.java b/src/me/mctp/utils/MultiPageGUI.java new file mode 100644 index 0000000..9264304 --- /dev/null +++ b/src/me/mctp/utils/MultiPageGUI.java @@ -0,0 +1,57 @@ +package me.mctp.utils; + +import me.mctp.xutils.SkullUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class MultiPageGUI { + private List inventories = new ArrayList<>(); + + public MultiPageGUI(String title, int itemsPerPage, List items) { + if (!title.contains("%page%")) { + throw new IllegalArgumentException("Title must contain %page% for the page."); + } + + int invSize = itemsPerPage + 9; + + if (itemsPerPage%9 != 0) { + throw new IllegalArgumentException("ItemsPerPage must be divisible by 9."); + } + + int neededInvs = ((int) Math.ceil(items.size() / invSize)); + + for (int i = 0; i < neededInvs; i++) { + //Create inv + Inventory inv = Bukkit.createInventory(null, invSize - 1, title.replace("%page%", String.valueOf(i + 1))); + inventories.add(inv); + + //Add navigation + ItemStack previous = SkullUtils.getSkull(UUID.fromString("a68f0b64-8d14-4000-a95f-4b9ba14f8df9")); + ItemMeta previousMeta = previous.getItemMeta(); + previousMeta.setDisplayName(ChatColor.GOLD + "<< Vorige Pagina"); + previous.setItemMeta(previousMeta); + + ItemStack close = new ItemStack(Material.BARRIER, 1); + ItemMeta closeMeta = close.getItemMeta(); + closeMeta.setDisplayName(ChatColor.RED + "Sluit Menu"); + close.setItemMeta(closeMeta); + + ItemStack next = SkullUtils.getSkull(UUID.fromString("50c8510b-5ea0-4d60-be9a-7d542d6cd156")); + ItemMeta nextMeta = next.getItemMeta(); + nextMeta.setDisplayName(ChatColor.GOLD + "Volgende Pagina >>"); + next.setItemMeta(nextMeta); + + inv.setItem(itemsPerPage, previous); + inv.setItem(itemsPerPage + 4, close); + inv.setItem(itemsPerPage + 8, next); + } + } +} diff --git a/src/me/mctp/xutils/ReflectionUtils.java b/src/me/mctp/xutils/ReflectionUtils.java new file mode 100644 index 0000000..d544b77 --- /dev/null +++ b/src/me/mctp/xutils/ReflectionUtils.java @@ -0,0 +1,144 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package me.mctp.xutils; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.concurrent.CompletableFuture; + +/** + * ReflectionUtils - Reflection handler for NMS and CraftBukkit.
+ * Caches the packet related methods and is asynchronous. + *

+ * This class does not handle null checks as most of the requests are from the + * other utility classes that already handle null checks. + *

+ * Clientbound Packets are considered fake + * updates to the client without changing the actual data. Since all the data is handled + * by the server. + * + * @author Crypto Morin + * @version 1.0.2 + */ +public class ReflectionUtils { + /** + * We use reflection mainly to avoid writing a new class for version barrier. + * The version barrier is for NMS that uses the Minecraft version as the main package name. + *

+ * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} + * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} + * In order to maintain cross-version compatibility we cannot import these classes. + */ + public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + public static final String CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.'; + public static final String NMS = "net.minecraft.server." + VERSION + '.'; + + private static final MethodHandle PLAYER_CONNECTION; + private static final MethodHandle GET_HANDLE; + private static final MethodHandle SEND_PACKET; + + static { + Class entityPlayer = getNMSClass("EntityPlayer"); + Class craftPlayer = getCraftClass("entity.CraftPlayer"); + Class playerConnection = getNMSClass("PlayerConnection"); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle sendPacket = null; + MethodHandle getHandle = null; + MethodHandle connection = null; + try { + connection = lookup.findGetter(entityPlayer, "playerConnection", playerConnection); + getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); + sendPacket = lookup.findVirtual(playerConnection, "sendPacket", MethodType.methodType(void.class, getNMSClass("Packet"))); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + + PLAYER_CONNECTION = connection; + SEND_PACKET = sendPacket; + GET_HANDLE = getHandle; + } + + /** + * Get a NMS (net.minecraft.server) class. + * + * @param name the name of the class. + * @return the class. + * @since 1.0.0 + */ + @Nullable + public static Class getNMSClass(@Nonnull String name) { + try { + return Class.forName(NMS + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + /** + * Sends a packet to the player asynchronously. + * Packets are thread-safe. + * + * @param player the player to send the packet to. + * @param packets the packets to send. + * @return the async thread handling the packet. + * @since 1.0.0 + */ + @Nonnull + public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { + return CompletableFuture.runAsync(() -> { + try { + Object handle = GET_HANDLE.invoke(player); + Object connection = PLAYER_CONNECTION.invoke(handle); + if (player.isOnline()) { + for (Object packet : packets) SEND_PACKET.invoke(connection, packet); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + }); + } + + /** + * Get a CraftBukkit class. + * + * @param name the name of the class. + * @return a class. + * @since 1.0.0 + */ + @Nullable + public static Class getCraftClass(@Nonnull String name) { + try { + return Class.forName(CRAFTBUKKIT + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/src/me/mctp/xutils/SkullCacheListener.java b/src/me/mctp/xutils/SkullCacheListener.java new file mode 100644 index 0000000..da6e8f4 --- /dev/null +++ b/src/me/mctp/xutils/SkullCacheListener.java @@ -0,0 +1,123 @@ +package me.mctp.xutils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.authlib.GameProfile; +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * This class is currently unused until I find a solution. + */ +@SuppressWarnings("unused") +final class SkullCacheListener { + protected static final Map CACHE = new HashMap<>(); + private static final String SESSION = "https://sessionserver.mojang.com/session/minecraft/profile/"; + + /** + * https://api.mojang.com/users/profiles/minecraft/Username gives the ID + * https://api.mojang.com/user/profiles/ID without dashes/names gives the names used for the unique ID. + * https://sessionserver.mojang.com/session/minecraft/profile/ID example data: + *

+ *

+     * {
+     *      "id": "Without dashes -",
+     *      "name": "",
+     *      "properties": [
+     *      {
+     *          "name": "textures",
+     *          "value": ""
+     *      }
+     *      ]
+     * }
+     * 
+ */ + @Nullable + public static String getSkinValue(@Nonnull String id) { + Objects.requireNonNull(id, "Player UUID cannot be null"); + + try { + JsonParser parser = new JsonParser(); + URL properties = new URL(SESSION + id); // + "?unsigned=false" + try (InputStreamReader readProperties = new InputStreamReader(properties.openStream())) { + JsonObject jObjectP = parser.parse(readProperties).getAsJsonObject(); + + if (mojangError(jObjectP)) return null; + JsonObject textureProperty = jObjectP.get("properties").getAsJsonArray().get(0).getAsJsonObject(); + //String signature = textureProperty.get("signature").getAsString(); + return textureProperty.get("value").getAsString(); + } + } catch (IOException | IllegalStateException e) { + System.err.println("Could not get skin data from session servers! " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + @Nullable + public static String getIdFromUsername(@Nonnull String username) { + Validate.notEmpty(username, "Cannot get UUID of a null or empty username"); + int len = username.length(); + if (len < 3 || len > 16) throw new IllegalArgumentException("Username cannot be less than 3 and longer than 16 characters: " + username); + + try { + URL convertName = new URL("https://api.mojang.com/users/profiles/minecraft/" + username); + JsonParser parser = new JsonParser(); + + try (InputStreamReader idReader = new InputStreamReader(convertName.openStream())) { + JsonElement jElement = parser.parse(idReader); + if (!jElement.isJsonObject()) return null; + + JsonObject jObject = jElement.getAsJsonObject(); + if (mojangError(jObject)) return null; + return jObject.get("id").getAsString(); + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private static boolean mojangError(@Nonnull JsonObject jsonObject) { + if (!jsonObject.has("error")) return false; + + String err = jsonObject.get("error").getAsString(); + String msg = jsonObject.get("errorMessage").getAsString(); + System.err.println("Mojang Error " + err + ": " + msg); + return true; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + GameProfile profile = new GameProfile(player.getUniqueId(), player.getName()); + ItemStack head = XMaterial.PLAYER_HEAD.parseItem(); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + try { + SkullUtils.GAME_PROFILE.invoke(meta, profile); + } catch (Throwable ex) { + ex.printStackTrace(); + } + head.setItemMeta(meta); + + // If you don't add it to the players inventory, it won't be cached. That's the problem. + // Or is the inventory cached? I tested this with multiple inventories and other inventories load immediately after an inventory with + // the skull in it is opened once. + player.getInventory().addItem(head); + } +} \ No newline at end of file diff --git a/src/me/mctp/xutils/SkullUtils.java b/src/me/mctp/xutils/SkullUtils.java new file mode 100644 index 0000000..19557d2 --- /dev/null +++ b/src/me/mctp/xutils/SkullUtils.java @@ -0,0 +1,191 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package me.mctp.xutils; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.Base64; +import java.util.Objects; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * SkullUtils - Apply skull texture from different sources.
+ * Skull Meta: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/inventory/meta/SkullMeta.html + * Mojang API: https://wiki.vg/Mojang_API + * + * @author Crypto Morin + * @version 3.0.1 + * @see XMaterial + */ +public class SkullUtils { + protected static final MethodHandle GAME_PROFILE; + private static final String VALUE_PROPERTY = "{\"textures\":{\"SKIN\":{\"url\":\""; + private static final boolean SUPPORTS_UUID = XMaterial.supports(12); + private static final String TEXTURES = "https://textures.minecraft.net/texture/"; + private static final Pattern BASE64 = Pattern.compile("(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?"); + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle gameProfile = null; + + try { + Class craftSkull = ReflectionUtils.getCraftClass("inventory.CraftMetaSkull"); + Field profileField = craftSkull.getDeclaredField("profile"); + profileField.setAccessible(true); + gameProfile = lookup.unreflectSetter(profileField); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + GAME_PROFILE = gameProfile; + } + + @SuppressWarnings("deprecation") + @Nonnull + public static ItemStack getSkull(@Nonnull UUID id) { + ItemStack head = XMaterial.PLAYER_HEAD.parseItem(); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + + if (SUPPORTS_UUID) meta.setOwningPlayer(Bukkit.getOfflinePlayer(id)); + else meta.setOwner(id.toString()); + + head.setItemMeta(meta); + return head; + } + + @Nonnull + public static SkullMeta applyCachedSkin(@Nonnull ItemMeta head, @Nonnull UUID identifier) { + String base64 = SkullCacheListener.CACHE.get(identifier); + SkullMeta meta = (SkullMeta) head; + return getSkullByValue(meta, base64); + } + + @SuppressWarnings("deprecation") + @Nonnull + public static SkullMeta applySkin(@Nonnull ItemMeta head, @Nonnull OfflinePlayer identifier) { + SkullMeta meta = (SkullMeta) head; + if (SUPPORTS_UUID) { + meta.setOwningPlayer(identifier); + } else { + meta.setOwner(identifier.getName()); + } + return meta; + } + + @Nonnull + public static SkullMeta applySkin(@Nonnull ItemMeta head, @Nonnull UUID identifier) { + return applySkin(head, Bukkit.getOfflinePlayer(identifier)); + } + + @SuppressWarnings("deprecation") + @Nonnull + public static SkullMeta applySkin(@Nonnull ItemMeta head, @Nonnull String identifier) { + SkullMeta meta = (SkullMeta) head; + if (isUsername(identifier)) return applySkin(head, Bukkit.getOfflinePlayer(identifier)); + if (identifier.contains("textures.minecraft.net")) return getValueFromTextures(meta, identifier); + if (identifier.length() > 100 && isBase64(identifier)) return getSkullByValue(meta, identifier); + return getTexturesFromUrlValue(meta, identifier); + } + + @Nonnull + private static SkullMeta getSkullByValue(@Nonnull SkullMeta head, @Nonnull String value) { + Validate.notEmpty(value, "Skull value cannot be null or empty"); + GameProfile profile = new GameProfile(UUID.randomUUID(), null); + profile.getProperties().put("textures", new Property("textures", value)); + + try { + GAME_PROFILE.invoke(head, profile); + } catch (Throwable ex) { + ex.printStackTrace(); + } + + return head; + } + + @Nonnull + private static SkullMeta getValueFromTextures(@Nonnull SkullMeta head, @Nonnull String url) { + return getSkullByValue(head, encodeBase64(VALUE_PROPERTY + url + "\"}}}")); + } + + @Nonnull + private static SkullMeta getTexturesFromUrlValue(@Nonnull SkullMeta head, @Nonnull String urlValue) { + return getValueFromTextures(head, TEXTURES + urlValue); + } + + @Nonnull + private static String encodeBase64(@Nonnull String str) { + return Base64.getEncoder().encodeToString(str.getBytes()); + } + + private static boolean isBase64(@Nonnull String base64) { + return BASE64.matcher(base64).matches(); + } + + @Nullable + public static String getSkinValue(@Nonnull ItemStack skull) { + Objects.requireNonNull(skull, "Skull ItemStack cannot be null"); + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + GameProfile profile = null; + + try { + Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profile = (GameProfile) profileField.get(meta); + } catch (SecurityException | NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + + if (profile != null && !profile.getProperties().get("textures").isEmpty()) + for (Property property : profile.getProperties().get("textures")) + if (!property.getValue().isEmpty()) + return property.getValue(); + + return null; + } + + private static boolean isUsername(@Nullable String name) { + if (Strings.isNullOrEmpty(name)) return false; + int len = name.length(); + if (len < 3 || len > 16) return false; + + // For some reasons Apache's Lists.charactersOf is faster than character indexing for small strings. + for (char ch : Lists.charactersOf(name)) { + if (ch != '_' && !(ch >= 'A' && ch <= 'Z') && !(ch >= 'a' && ch <= 'z') && !(ch >= '0' && ch <= '9')) return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/me/mctp/xutils/XMaterial.java b/src/me/mctp/xutils/XMaterial.java new file mode 100644 index 0000000..e042401 --- /dev/null +++ b/src/me/mctp/xutils/XMaterial.java @@ -0,0 +1,2156 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Hex_27 + * Copyright (c) 2020 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package me.mctp.xutils; + +import com.google.common.base.Enums; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.apache.commons.lang.WordUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * XMaterial - Data Values/Pre-flattening
+ * 1.13 and above as priority. + *

+ * This class is mainly designed to support ItemStacks. If you want to use it on blocks, you'll have to use + * XBlock + *

+ * Pre-flattening: https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening + * Materials: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + * Materials (1.12): https://helpch.at/docs/1.12.2/index.html?org/bukkit/Material.html + * Material IDs: https://minecraft-ids.grahamedgecombe.com/ + * Material Source Code: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java + * XMaterial v1: https://www.spigotmc.org/threads/329630/ + *

+ * This class will throw a "unsupported material" error if someone tries to use an item with an invalid data value which can only happen in 1.12 servers and below. + * To get an invalid item, (aka Missing Texture Block) you can use the command + * /give @p minecraft:dirt 1 10 where 1 is the item amount, and 10 is the data value. The material {@link #DIRT} with a data value of {@code 10} doesn't exist. + * + * @author Crypto Morin + * @version 6.0.2 + * @see Material + * @see ItemStack + */ +public enum XMaterial { + ACACIA_BOAT("BOAT_ACACIA"), + ACACIA_BUTTON("WOOD_BUTTON"), + ACACIA_DOOR("ACACIA_DOOR_ITEM", "ACACIA_DOOR"), + ACACIA_FENCE, + ACACIA_FENCE_GATE, + ACACIA_LEAVES("LEAVES_2"), + ACACIA_LOG("LOG_2"), + ACACIA_PLANKS(4, "WOOD"), + ACACIA_PRESSURE_PLATE("WOOD_PLATE"), + ACACIA_SAPLING(4, "SAPLING"), + ACACIA_SIGN("SIGN_POST", "SIGN"), + ACACIA_SLAB(4, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + ACACIA_STAIRS, + ACACIA_TRAPDOOR("TRAP_DOOR"), + ACACIA_WALL_SIGN("WALL_SIGN"), + ACACIA_WOOD("LOG_2"), + ACTIVATOR_RAIL, + /** + * https://minecraft.gamepedia.com/Air + * {@link Material#isAir()} + * + * @see #VOID_AIR + * @see #CAVE_AIR + */ + AIR, + ALLIUM(2, "RED_ROSE"), + ANCIENT_DEBRIS("1.16"), + ANDESITE(5, "STONE"), + ANDESITE_SLAB, + ANDESITE_STAIRS, + ANDESITE_WALL, + ANVIL, + APPLE, + ARMOR_STAND, + ARROW, + ATTACHED_MELON_STEM(7, "MELON_STEM"), + ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"), + AZURE_BLUET(3, "RED_ROSE"), + BAKED_POTATO, + BAMBOO("1.14", "SUGAR_CANE", ""), + BAMBOO_SAPLING("1.14"), + BARREL("1.14", "CHEST", ""), + BARRIER, + BASALT("1.16"), + BAT_SPAWN_EGG(65, "MONSTER_EGG"), + BEACON, + BEDROCK, + BEEF("RAW_BEEF"), + BEEHIVE("1.15"), + /** + * Beetroot is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + BEETROOT("BEETROOT_BLOCK"), + BEETROOTS("BEETROOT"), + BEETROOT_SEEDS, + BEETROOT_SOUP, + BEE_NEST("1.15"), + BEE_SPAWN_EGG("1.15"), + BELL("1.14"), + BIRCH_BOAT("BOAT_BIRCH"), + BIRCH_BUTTON("WOOD_BUTTON"), + BIRCH_DOOR("BIRCH_DOOR_ITEM", "BIRCH_DOOR"), + BIRCH_FENCE, + BIRCH_FENCE_GATE, + BIRCH_LEAVES(2, "LEAVES"), + BIRCH_LOG(2, "LOG"), + BIRCH_PLANKS(2, "WOOD"), + BIRCH_PRESSURE_PLATE("WOOD_PLATE"), + BIRCH_SAPLING(2, "SAPLING"), + BIRCH_SIGN("SIGN_POST", "SIGN"), + BIRCH_SLAB(2, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + BIRCH_STAIRS("BIRCH_WOOD_STAIRS"), + BIRCH_TRAPDOOR("TRAP_DOOR"), + BIRCH_WALL_SIGN("WALL_SIGN"), + BIRCH_WOOD(2, "LOG"), + BLACKSTONE("1.16"), + BLACKSTONE_SLAB("1.16"), + BLACKSTONE_STAIRS("1.16"), + BLACKSTONE_WALL("1.16"), + BLACK_BANNER("STANDING_BANNER", "BANNER"), + BLACK_BED(15, "BED_BLOCK", "BED"), + BLACK_CARPET(15, "CARPET"), + BLACK_CONCRETE(15, "CONCRETE"), + BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"), + BLACK_DYE("1.14", "INK_SACK", "INK_SAC"), + BLACK_GLAZED_TERRACOTTA(15, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLACK_TERRACOTTA"), + BLACK_SHULKER_BOX, + BLACK_STAINED_GLASS(15, "STAINED_GLASS"), + BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"), + BLACK_TERRACOTTA(15, "HARD_CLAY", "STAINED_CLAY"), + BLACK_WALL_BANNER("WALL_BANNER"), + BLACK_WOOL(15, "WOOL"), + BLAST_FURNACE("1.14", "FURNACE", ""), + BLAZE_POWDER, + BLAZE_ROD, + BLAZE_SPAWN_EGG(61, "MONSTER_EGG"), + BLUE_BANNER(4, "STANDING_BANNER", "BANNER"), + BLUE_BED(11, "BED_BLOCK", "BED"), + BLUE_CARPET(11, "CARPET"), + BLUE_CONCRETE(11, "CONCRETE"), + BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"), + BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"), + BLUE_GLAZED_TERRACOTTA(11, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLUE_TERRACOTTA"), + BLUE_ICE("1.13", "PACKED_ICE", ""), + BLUE_ORCHID(1, "RED_ROSE"), + BLUE_SHULKER_BOX, + BLUE_STAINED_GLASS(11, "STAINED_GLASS"), + BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"), + BLUE_TERRACOTTA(11, "HARD_CLAY", "STAINED_CLAY"), + BLUE_WALL_BANNER(4, "WALL_BANNER"), + BLUE_WOOL(11, "WOOL"), + BONE, + BONE_BLOCK, + BONE_MEAL(15, "INK_SACK"), + BOOK, + BOOKSHELF, + BOW, + BOWL, + BRAIN_CORAL("1.13"), + BRAIN_CORAL_BLOCK("1.13"), + BRAIN_CORAL_FAN("1.13"), + BRAIN_CORAL_WALL_FAN, + BREAD, + BREWING_STAND("BREWING_STAND_ITEM"), + BRICK("CLAY_BRICK"), + BRICKS("BRICK"), + BRICK_SLAB(4, "STEP"), + BRICK_STAIRS, + BRICK_WALL, + BROWN_BANNER(3, "STANDING_BANNER", "BANNER"), + BROWN_BED(12, "BED_BLOCK", "BED"), + BROWN_CARPET(12, "CARPET"), + BROWN_CONCRETE(12, "CONCRETE"), + BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"), + BROWN_DYE(3, "INK_SACK", "COCOA", "COCOA_BEANS"), + BROWN_GLAZED_TERRACOTTA(12, "1.12", "HARD_CLAY", "STAINED_CLAY", "BROWN_TERRACOTTA"), + BROWN_MUSHROOM, + BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"), + BROWN_SHULKER_BOX, + BROWN_STAINED_GLASS(12, "STAINED_GLASS"), + BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"), + BROWN_TERRACOTTA(12, "STAINED_CLAY"), + BROWN_WALL_BANNER(3, "WALL_BANNER"), + BROWN_WOOL(12, "WOOL"), + BUBBLE_COLUMN("1.13"), + BUBBLE_CORAL("1.13"), + BUBBLE_CORAL_BLOCK("1.13"), + BUBBLE_CORAL_FAN("1.13"), + BUBBLE_CORAL_WALL_FAN, + BUCKET, + CACTUS, + CAKE("CAKE_BLOCK"), + CAMPFIRE("1.14"), + CARROT("CARROT_ITEM"), + CARROTS("CARROT"), + CARROT_ON_A_STICK("CARROT_STICK"), + CARTOGRAPHY_TABLE("1.14", "CRAFTING_TABLE", ""), + CARVED_PUMPKIN(1, "1.13", "PUMPKIN", ""), + CAT_SPAWN_EGG, + CAULDRON("CAULDRON_ITEM"), + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #VOID_AIR + */ + CAVE_AIR("AIR"), + CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"), + CHAIN("1.16"), + CHAINMAIL_BOOTS, + CHAINMAIL_CHESTPLATE, + CHAINMAIL_HELMET, + CHAINMAIL_LEGGINGS, + CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"), + CHARCOAL(1, "COAL"), + CHEST("LOCKED_CHEST"), + CHEST_MINECART("STORAGE_MINECART"), + CHICKEN("RAW_CHICKEN"), + CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"), + CHIPPED_ANVIL(1, "ANVIL"), + CHISELED_NETHER_BRICKS(1, "NETHER_BRICKS"), + CHISELED_POLISHED_BLACKSTONE("1.16", "POLISHED_BLACKSTONE"), + CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"), + CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"), + CHISELED_SANDSTONE(1, "SANDSTONE"), + CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"), + CHORUS_FLOWER("1.9"), + CHORUS_FRUIT("1.9"), + CHORUS_PLANT("1.9"), + CLAY, + CLAY_BALL, + CLOCK("WATCH"), + COAL, + COAL_BLOCK, + COAL_ORE, + COARSE_DIRT(1, "DIRT"), + COBBLESTONE, + COBBLESTONE_SLAB(3, "STEP"), + COBBLESTONE_STAIRS, + COBBLESTONE_WALL("COBBLE_WALL"), + COBWEB("WEB"), + COCOA("1.15"), + COCOA_BEANS(3, "INK_SACK", "COCOA"), + COD("RAW_FISH"), + COD_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + COD_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + COMMAND_BLOCK("COMMAND"), + COMMAND_BLOCK_MINECART("COMMAND_MINECART"), + /** + * Unlike redstone torch and redstone lamp... neither REDTONE_COMPARATOR_OFF nor REDSTONE_COMPARATOR_ON + * are items. REDSTONE_COMPARATOR is. + * + * @see #REDSTONE_TORCH + * @see #REDSTONE_LAMP + */ + COMPARATOR("REDSTONE_COMPARATOR_OFF", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR"), + COMPASS, + COMPOSTER("1.14", "CAULDRON", ""), + CONDUIT("1.13", "BEACON"), + COOKED_BEEF, + COOKED_CHICKEN, + COOKED_COD("COOKED_FISH"), + COOKED_MUTTON, + COOKED_PORKCHOP("PORK", "GRILLED_PORK"), + COOKED_RABBIT, + COOKED_SALMON(1, "COOKED_FISH"), + COOKIE, + CORNFLOWER(4, "1.14", "BLUE_DYE", ""), + COW_SPAWN_EGG(92, "MONSTER_EGG"), + CRACKED_NETHER_BRICKS(2, "NETHER_BRICKS"), + CRACKED_POLISHED_BLACKSTONE_BRICKS("1.16", "POLISHED_BLACKSTONE_BRICKS"), + CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"), + CRAFTING_TABLE("WORKBENCH"), + CREEPER_BANNER_PATTERN, + CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"), + CREEPER_SPAWN_EGG(50, "MONSTER_EGG"), + CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"), + CRIMSON_BUTTON("1.16"), + CRIMSON_DOOR("1.16"), + CRIMSON_FENCE("1.16"), + CRIMSON_FENCE_GATE("1.16"), + CRIMSON_FUNGUS("1.16"), + CRIMSON_HYPHAE("1.16"), + CRIMSON_NYLIUM("1.16"), + CRIMSON_PLANKS("1.16"), + CRIMSON_PRESSURE_PLATE("1.16"), + CRIMSON_ROOTS("1.16"), + CRIMSON_SIGN("1.16", "SIGN_POST"), + CRIMSON_SLAB("1.16"), + CRIMSON_STAIRS("1.16"), + CRIMSON_STEM("1.16"), + CRIMSON_TRAPDOOR("1.16"), + CRIMSON_WALL_SIGN("1.16", "WALL_SIGN"), + CROSSBOW, + CRYING_OBSIDIAN("1.16"), + CUT_RED_SANDSTONE("1.13"), + CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"), + CUT_SANDSTONE("1.13"), + CUT_SANDSTONE_SLAB("STEP"), + CYAN_BANNER(6, "STANDING_BANNER", "BANNER"), + CYAN_BED(9, "BED_BLOCK", "BED"), + CYAN_CARPET(9, "CARPET"), + CYAN_CONCRETE(9, "CONCRETE"), + CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"), + CYAN_DYE(6, "INK_SACK"), + CYAN_GLAZED_TERRACOTTA(9, "1.12", "HARD_CLAY", "STAINED_CLAY", "CYAN_TERRACOTTA"), + CYAN_SHULKER_BOX, + CYAN_STAINED_GLASS(9, "STAINED_GLASS"), + CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"), + CYAN_TERRACOTTA(9, "HARD_CLAY", "STAINED_CLAY"), + CYAN_WALL_BANNER(6, "WALL_BANNER"), + CYAN_WOOL(9, "WOOL"), + DAMAGED_ANVIL(2, "ANVIL"), + DANDELION("YELLOW_FLOWER"), + DARK_OAK_BOAT("BOAT_DARK_OAK"), + DARK_OAK_BUTTON("WOOD_BUTTON"), + DARK_OAK_DOOR("DARK_OAK_DOOR_ITEM", "DARK_OAK_DOOR"), + DARK_OAK_FENCE, + DARK_OAK_FENCE_GATE, + DARK_OAK_LEAVES(4, "LEAVES", "LEAVES_2"), + DARK_OAK_LOG(1, "LOG", "LOG_2"), + DARK_OAK_PLANKS(5, "WOOD"), + DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"), + DARK_OAK_SAPLING(5, "SAPLING"), + DARK_OAK_SIGN("SIGN_POST", "SIGN"), + DARK_OAK_SLAB(5, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + DARK_OAK_STAIRS, + DARK_OAK_TRAPDOOR("TRAP_DOOR"), + DARK_OAK_WALL_SIGN("WALL_SIGN"), + DARK_OAK_WOOD(1, "LOG", "LOG_2"), + DARK_PRISMARINE(1, "PRISMARINE"), + DARK_PRISMARINE_SLAB("1.13"), + DARK_PRISMARINE_STAIRS("1.13"), + DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"), + DEAD_BRAIN_CORAL("1.13"), + DEAD_BRAIN_CORAL_BLOCK("1.13"), + DEAD_BRAIN_CORAL_FAN("1.13"), + DEAD_BRAIN_CORAL_WALL_FAN("1.13"), + DEAD_BUBBLE_CORAL("1.13"), + DEAD_BUBBLE_CORAL_BLOCK("1.13"), + DEAD_BUBBLE_CORAL_FAN("1.13"), + DEAD_BUBBLE_CORAL_WALL_FAN("1.13"), + DEAD_BUSH, + DEAD_FIRE_CORAL("1.13"), + DEAD_FIRE_CORAL_BLOCK("1.13"), + DEAD_FIRE_CORAL_FAN("1.13"), + DEAD_FIRE_CORAL_WALL_FAN("1.13"), + DEAD_HORN_CORAL("1.13"), + DEAD_HORN_CORAL_BLOCK("1.13"), + DEAD_HORN_CORAL_FAN("1.13"), + DEAD_HORN_CORAL_WALL_FAN("1.13"), + DEAD_TUBE_CORAL("1.13"), + DEAD_TUBE_CORAL_BLOCK("1.13"), + DEAD_TUBE_CORAL_FAN("1.13"), + DEAD_TUBE_CORAL_WALL_FAN("1.13"), + DEBUG_STICK("1.13", "STICK", ""), + DETECTOR_RAIL, + DIAMOND, + DIAMOND_AXE, + DIAMOND_BLOCK, + DIAMOND_BOOTS, + DIAMOND_CHESTPLATE, + DIAMOND_HELMET, + DIAMOND_HOE, + DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"), + DIAMOND_LEGGINGS, + DIAMOND_ORE, + DIAMOND_PICKAXE, + DIAMOND_SHOVEL("DIAMOND_SPADE"), + DIAMOND_SWORD, + DIORITE(3, "STONE"), + DIORITE_SLAB, + DIORITE_STAIRS, + DIORITE_WALL, + DIRT, + DISPENSER, + DOLPHIN_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + DONKEY_SPAWN_EGG(32, "MONSTER_EGG"), + DRAGON_BREATH("DRAGONS_BREATH"), + DRAGON_EGG, + DRAGON_HEAD(5, "1.9", "SKULL", "SKULL_ITEM"), + DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"), + DRIED_KELP("1.13"), + DRIED_KELP_BLOCK("1.13"), + DROPPER, + DROWNED_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + EGG, + ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"), + ELYTRA, + EMERALD, + EMERALD_BLOCK, + EMERALD_ORE, + ENCHANTED_BOOK, + ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"), + ENCHANTING_TABLE("ENCHANTMENT_TABLE"), + ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"), + ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"), + ENDER_CHEST, + ENDER_EYE("EYE_OF_ENDER"), + ENDER_PEARL, + END_CRYSTAL, + END_GATEWAY("1.9"), + END_PORTAL("ENDER_PORTAL"), + END_PORTAL_FRAME("ENDER_PORTAL_FRAME"), + END_ROD("1.9", "BLAZE_ROD", ""), + END_STONE("ENDER_STONE"), + END_STONE_BRICKS("END_BRICKS"), + END_STONE_BRICK_SLAB(6, "STEP"), + END_STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + END_STONE_BRICK_WALL, + EVOKER_SPAWN_EGG(34, "MONSTER_EGG"), + EXPERIENCE_BOTTLE("EXP_BOTTLE"), + FARMLAND("SOIL"), + FEATHER, + FERMENTED_SPIDER_EYE, + FERN(2, "LONG_GRASS"), + /** + * For some reasons filled map items are really special. + * Their data value starts from 0 and every time a player + * creates a new map that maps data value increases. + */ + FILLED_MAP("MAP"), + FIRE, + FIREWORK_ROCKET("FIREWORK"), + FIREWORK_STAR("FIREWORK_CHARGE"), + FIRE_CHARGE("FIREBALL"), + FIRE_CORAL("1.13"), + FIRE_CORAL_BLOCK("1.13"), + FIRE_CORAL_FAN("1.13"), + FIRE_CORAL_WALL_FAN, + FISHING_ROD, + FLETCHING_TABLE("1.14", "CRAFTING_TABLE", ""), + FLINT, + FLINT_AND_STEEL, + FLOWER_BANNER_PATTERN, + FLOWER_POT("FLOWER_POT_ITEM"), + FOX_SPAWN_EGG("1.14"), + /** + * This special material cannot be obtained as an item. + */ + FROSTED_ICE("1.9", "PACKED_ICE", ""), + FURNACE("BURNING_FURNACE"), + FURNACE_MINECART("POWERED_MINECART"), + GHAST_SPAWN_EGG(56, "MONSTER_EGG"), + GHAST_TEAR, + GILDED_BLACKSTONE("1.16"), + GLASS, + GLASS_BOTTLE, + GLASS_PANE("THIN_GLASS"), + GLISTERING_MELON_SLICE("SPECKLED_MELON"), + GLOBE_BANNER_PATTERN, + GLOWSTONE, + GLOWSTONE_DUST, + GOLDEN_APPLE, + GOLDEN_AXE("GOLD_AXE"), + GOLDEN_BOOTS("GOLD_BOOTS"), + GOLDEN_CARROT, + GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"), + GOLDEN_HELMET("GOLD_HELMET"), + GOLDEN_HOE("GOLD_HOE"), + GOLDEN_HORSE_ARMOR("GOLD_BARDING"), + GOLDEN_LEGGINGS("GOLD_LEGGINGS"), + GOLDEN_PICKAXE("GOLD_PICKAXE"), + GOLDEN_SHOVEL("GOLD_SPADE"), + GOLDEN_SWORD("GOLD_SWORD"), + GOLD_BLOCK, + GOLD_INGOT, + GOLD_NUGGET, + GOLD_ORE, + GRANITE(1, "STONE"), + GRANITE_SLAB, + GRANITE_STAIRS, + GRANITE_WALL, + GRASS(1, "LONG_GRASS"), + GRASS_BLOCK("GRASS"), + GRASS_PATH, + GRAVEL, + GRAY_BANNER(8, "STANDING_BANNER", "BANNER"), + GRAY_BED(7, "BED_BLOCK", "BED"), + GRAY_CARPET(7, "CARPET"), + GRAY_CONCRETE(7, "CONCRETE"), + GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"), + GRAY_DYE(8, "INK_SACK"), + GRAY_GLAZED_TERRACOTTA(7, "1.12", "HARD_CLAY", "STAINED_CLAY", "GRAY_TERRACOTTA"), + GRAY_SHULKER_BOX, + GRAY_STAINED_GLASS(7, "STAINED_GLASS"), + GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"), + GRAY_TERRACOTTA(7, "HARD_CLAY", "STAINED_CLAY"), + GRAY_WALL_BANNER(8, "WALL_BANNER"), + GRAY_WOOL(7, "WOOL"), + GREEN_BANNER(2, "STANDING_BANNER", "BANNER"), + GREEN_BED(13, "BED_BLOCK", "BED"), + GREEN_CARPET(13, "CARPET"), + GREEN_CONCRETE(13, "CONCRETE"), + GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"), + GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"), + GREEN_GLAZED_TERRACOTTA(13, "1.12", "HARD_CLAY", "STAINED_CLAY", "GREEN_TERRACOTTA"), + GREEN_SHULKER_BOX, + GREEN_STAINED_GLASS(13, "STAINED_GLASS"), + GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"), + GREEN_TERRACOTTA(13, "HARD_CLAY", "STAINED_CLAY"), + GREEN_WALL_BANNER(2, "WALL_BANNER"), + GREEN_WOOL(13, "WOOL"), + GRINDSTONE("1.14", "ANVIL", ""), + GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"), + GUNPOWDER("SULPHUR"), + HAY_BLOCK, + HEART_OF_THE_SEA("1.13"), + HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"), + HOGLIN_SPAWN_EGG("1.16", "MONSTER_EGG"), + HONEYCOMB("1.15"), + HONEYCOMB_BLOCK("1.15"), + HONEY_BLOCK("1.15", "SLIME_BLOCK", ""), + HONEY_BOTTLE("1.15", "GLASS_BOTTLE", ""), + HOPPER, + HOPPER_MINECART, + HORN_CORAL("1.13"), + HORN_CORAL_BLOCK("1.13"), + HORN_CORAL_FAN("1.13"), + HORN_CORAL_WALL_FAN, + HORSE_SPAWN_EGG(100, "MONSTER_EGG"), + HUSK_SPAWN_EGG(23, "MONSTER_EGG"), + ICE, + INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS", "SMOOTH_BRICK"), + INFESTED_COBBLESTONE(1, "MONSTER_EGGS"), + INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS", "SMOOTH_BRICK"), + INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"), + INFESTED_STONE("MONSTER_EGGS"), + INFESTED_STONE_BRICKS(2, "MONSTER_EGGS", "SMOOTH_BRICK"), + /** + * We will only add "INK_SAC" for {@link #BLACK_DYE} since it's + * the only material (linked with this material) that is added + * after 1.13, which means it can use both INK_SACK and INK_SAC. + */ + INK_SAC("INK_SACK"), + IRON_AXE, + IRON_BARS("IRON_FENCE"), + IRON_BLOCK, + IRON_BOOTS, + IRON_CHESTPLATE, + IRON_DOOR("IRON_DOOR_BLOCK"), + IRON_HELMET, + IRON_HOE, + IRON_HORSE_ARMOR("IRON_BARDING"), + IRON_INGOT, + IRON_LEGGINGS, + IRON_NUGGET, + IRON_ORE, + IRON_PICKAXE, + IRON_SHOVEL("IRON_SPADE"), + IRON_SWORD, + IRON_TRAPDOOR, + ITEM_FRAME, + JACK_O_LANTERN, + JIGSAW("1.14", "COMMAND_BLOCK", "STRUCTURE_BLOCK", ""), + JUKEBOX, + JUNGLE_BOAT("BOAT_JUNGLE"), + JUNGLE_BUTTON("WOOD_BUTTON"), + JUNGLE_DOOR("JUNGLE_DOOR_ITEM", "JUNGLE_DOOR"), + JUNGLE_FENCE, + JUNGLE_FENCE_GATE, + JUNGLE_LEAVES(3, "LEAVES"), + JUNGLE_LOG(3, "LOG"), + JUNGLE_PLANKS(3, "WOOD"), + JUNGLE_PRESSURE_PLATE("WOOD_PLATE"), + JUNGLE_SAPLING(3, "SAPLING"), + JUNGLE_SIGN("SIGN_POST", "SIGN"), + JUNGLE_SLAB(3, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"), + JUNGLE_TRAPDOOR("TRAP_DOOR"), + JUNGLE_WALL_SIGN("WALL_SIGN"), + JUNGLE_WOOD(3, "LOG"), + KELP("1.13"), + KELP_PLANT("1.13"), + KNOWLEDGE_BOOK("1.12", "BOOK"), + LADDER, + LANTERN("1.14", "SEA_LANTERN", ""), + LAPIS_BLOCK, + LAPIS_LAZULI(4, "INK_SACK"), + LAPIS_ORE, + LARGE_FERN(3, "DOUBLE_PLANT"), + LAVA("STATIONARY_LAVA"), + LAVA_BUCKET, + LEAD("LEASH"), + LEATHER, + LEATHER_BOOTS, + LEATHER_CHESTPLATE, + LEATHER_HELMET, + LEATHER_HORSE_ARMOR("1.14", "IRON_HORSE_ARMOR", ""), + LEATHER_LEGGINGS, + LECTERN("1.14", "BOOKSHELF", ""), + LEVER, + LIGHT_BLUE_BANNER(12, "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_BED(3, "BED_BLOCK", "BED"), + LIGHT_BLUE_CARPET(3, "CARPET"), + LIGHT_BLUE_CONCRETE(3, "CONCRETE"), + LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"), + LIGHT_BLUE_DYE(12, "INK_SACK"), + LIGHT_BLUE_GLAZED_TERRACOTTA(3, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_BLUE_TERRACOTTA"), + LIGHT_BLUE_SHULKER_BOX, + LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"), + LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"), + LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_WOOL(3, "WOOL"), + LIGHT_GRAY_BANNER(7, "STANDING_BANNER", "BANNER"), + LIGHT_GRAY_BED(8, "BED_BLOCK", "BED"), + LIGHT_GRAY_CARPET(8, "CARPET"), + LIGHT_GRAY_CONCRETE(8, "CONCRETE"), + LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"), + LIGHT_GRAY_DYE(7, "INK_SACK"), + /** + * Renamed to SILVER_GLAZED_TERRACOTTA in 1.12 + * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 + */ + LIGHT_GRAY_GLAZED_TERRACOTTA("1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_GRAY_TERRACOTTA", "SILVER_GLAZED_TERRACOTTA"), + LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"), + LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"), + LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_GRAY_TERRACOTTA(8, "HARD_CLAY", "STAINED_CLAY"), + LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"), + LIGHT_GRAY_WOOL(8, "WOOL"), + LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"), + LILAC(1, "DOUBLE_PLANT"), + LILY_OF_THE_VALLEY(15, "1.14", "WHITE_DYE", ""), + LILY_PAD("WATER_LILY"), + LIME_BANNER(10, "STANDING_BANNER", "BANNER"), + LIME_BED(5, "BED_BLOCK", "BED"), + LIME_CARPET(5, "CARPET"), + LIME_CONCRETE(5, "CONCRETE"), + LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"), + LIME_DYE(10, "INK_SACK"), + LIME_GLAZED_TERRACOTTA(5, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIME_TERRACOTTA"), + LIME_SHULKER_BOX, + LIME_STAINED_GLASS(5, "STAINED_GLASS"), + LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"), + LIME_TERRACOTTA(5, "HARD_CLAY", "STAINED_CLAY"), + LIME_WALL_BANNER(10, "WALL_BANNER"), + LIME_WOOL(5, "WOOL"), + LINGERING_POTION, + LLAMA_SPAWN_EGG(103, "MONSTER_EGG"), + LODESTONE("1.16"), + LOOM("1.14"), + MAGENTA_BANNER(13, "STANDING_BANNER", "BANNER"), + MAGENTA_BED(2, "BED_BLOCK", "BED"), + MAGENTA_CARPET(2, "CARPET"), + MAGENTA_CONCRETE(2, "CONCRETE"), + MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"), + MAGENTA_DYE(13, "INK_SACK"), + MAGENTA_GLAZED_TERRACOTTA(2, "1.12", "HARD_CLAY", "STAINED_CLAY", "MAGENTA_TERRACOTTA"), + MAGENTA_SHULKER_BOX, + MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"), + MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"), + MAGENTA_TERRACOTTA(2, "HARD_CLAY", "STAINED_CLAY"), + MAGENTA_WALL_BANNER(13, "WALL_BANNER"), + MAGENTA_WOOL(2, "WOOL"), + MAGMA_BLOCK("1.10", "MAGMA"), + MAGMA_CREAM, + MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"), + /** + * Adding this to the duplicated list will give you a filled map + * for 1.13+ versions and removing it from duplicated list will + * still give you a filled map in -1.12 versions. + * Since higher versions are our priority I'll keep 1.13+ support + * until I can come up with something to fix it. + */ + MAP("EMPTY_MAP"), + MELON("MELON_BLOCK"), + MELON_SEEDS, + MELON_SLICE("MELON"), + MELON_STEM, + MILK_BUCKET, + MINECART, + MOJANG_BANNER_PATTERN, + MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"), + MOSSY_COBBLESTONE, + MOSSY_COBBLESTONE_SLAB(3, "STEP"), + MOSSY_COBBLESTONE_STAIRS, + MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"), + MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"), + MOSSY_STONE_BRICK_SLAB(5, "STEP"), + MOSSY_STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + MOSSY_STONE_BRICK_WALL, + MOVING_PISTON("PISTON_BASE", "PISTON_MOVING_PIECE"), + MULE_SPAWN_EGG(32, "MONSTER_EGG"), + MUSHROOM_STEM("BROWN_MUSHROOM"), + MUSHROOM_STEW("MUSHROOM_SOUP"), + MUSIC_DISC_11("GOLD_RECORD"), + MUSIC_DISC_13("GREEN_RECORD"), + MUSIC_DISC_BLOCKS("RECORD_3"), + MUSIC_DISC_CAT("RECORD_4"), + MUSIC_DISC_CHIRP("RECORD_5"), + MUSIC_DISC_FAR("RECORD_6"), + MUSIC_DISC_MALL("RECORD_7"), + MUSIC_DISC_MELLOHI("RECORD_8"), + MUSIC_DISC_PIGSTEP("1.16"), + MUSIC_DISC_STAL("RECORD_9"), + MUSIC_DISC_STRAD("RECORD_10"), + MUSIC_DISC_WAIT("RECORD_11"), + MUSIC_DISC_WARD("RECORD_12"), + MUTTON, + MYCELIUM("MYCEL"), + NAME_TAG, + NAUTILUS_SHELL("1.13"), + NETHERITE_AXE("1.16"), + NETHERITE_BLOCK("1.16"), + NETHERITE_BOOTS("1.16"), + NETHERITE_CHESTPLATE("1.16"), + NETHERITE_HELMET("1.16"), + NETHERITE_HOE("1.16"), + NETHERITE_INGOT("1.16"), + NETHERITE_LEGGINGS("1.16"), + NETHERITE_PICKAXE("1.16"), + NETHERITE_SCRAP("1.16"), + NETHERITE_SHOVEL("1.16"), + NETHERITE_SWORD("1.16"), + NETHERRACK, + NETHER_BRICK("NETHER_BRICK_ITEM"), + NETHER_BRICKS("NETHER_BRICK"), + NETHER_BRICK_FENCE("NETHER_FENCE"), + NETHER_BRICK_SLAB(6, "STEP"), + NETHER_BRICK_STAIRS, + NETHER_BRICK_WALL, + NETHER_GOLD_ORE("1.16"), + NETHER_PORTAL("PORTAL"), + NETHER_QUARTZ_ORE("QUARTZ_ORE"), + NETHER_SPROUTS("1.16"), + NETHER_STAR, + /** + * Just like mentioned in https://minecraft.gamepedia.com/Nether_Wart + * Nether wart is also known as nether stalk in the code. + * NETHER_STALK is the planted state of nether warts. + */ + NETHER_WART("NETHER_WARTS", "NETHER_STALK"), + NETHER_WART_BLOCK, + NOTE_BLOCK, + OAK_BOAT("BOAT"), + OAK_BUTTON("WOOD_BUTTON"), + OAK_DOOR("WOOD_DOOR", "WOODEN_DOOR"), + OAK_FENCE("FENCE"), + OAK_FENCE_GATE("FENCE_GATE"), + OAK_LEAVES("LEAVES"), + OAK_LOG("LOG"), + OAK_PLANKS("WOOD"), + OAK_PRESSURE_PLATE("WOOD_PLATE"), + OAK_SAPLING("SAPLING"), + OAK_SIGN("SIGN_POST", "SIGN"), + OAK_SLAB("WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + OAK_STAIRS("WOOD_STAIRS"), + OAK_TRAPDOOR("TRAP_DOOR"), + OAK_WALL_SIGN("WALL_SIGN"), + OAK_WOOD("LOG"), + OBSERVER, + OBSIDIAN, + OCELOT_SPAWN_EGG(98, "MONSTER_EGG"), + ORANGE_BANNER(14, "STANDING_BANNER", "BANNER"), + ORANGE_BED(1, "BED_BLOCK", "BED"), + ORANGE_CARPET(1, "CARPET"), + ORANGE_CONCRETE(1, "CONCRETE"), + ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"), + ORANGE_DYE(14, "INK_SACK"), + ORANGE_GLAZED_TERRACOTTA(1, "1.12", "HARD_CLAY", "STAINED_CLAY", "ORANGE_TERRACOTTA"), + ORANGE_SHULKER_BOX, + ORANGE_STAINED_GLASS(1, "STAINED_GLASS"), + ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"), + ORANGE_TERRACOTTA(1, "HARD_CLAY", "STAINED_CLAY"), + ORANGE_TULIP(5, "RED_ROSE"), + ORANGE_WALL_BANNER(14, "WALL_BANNER"), + ORANGE_WOOL(1, "WOOL"), + OXEYE_DAISY(8, "RED_ROSE"), + PACKED_ICE, + PAINTING, + PANDA_SPAWN_EGG("1.14"), + PAPER, + PARROT_SPAWN_EGG(105, "MONSTER_EGG"), + PEONY(5, "DOUBLE_PLANT"), + PETRIFIED_OAK_SLAB("WOOD_STEP"), + PHANTOM_MEMBRANE("1.13"), + PHANTOM_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + PIGLIN_BANNER_PATTERN("1.16"), + PIGLIN_SPAWN_EGG(57, "MONSTER_EGG"), + PIG_SPAWN_EGG(90, "MONSTER_EGG"), + PILLAGER_SPAWN_EGG("1.14"), + PINK_BANNER(9, "STANDING_BANNER", "BANNER"), + PINK_BED(6, "BED_BLOCK", "BED"), + PINK_CARPET(6, "CARPET"), + PINK_CONCRETE(6, "CONCRETE"), + PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"), + PINK_DYE(9, "INK_SACK"), + PINK_GLAZED_TERRACOTTA(6, "1.12", "HARD_CLAY", "STAINED_CLAY", "PINK_TERRACOTTA"), + PINK_SHULKER_BOX, + PINK_STAINED_GLASS(6, "STAINED_GLASS"), + PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"), + PINK_TERRACOTTA(6, "HARD_CLAY", "STAINED_CLAY"), + PINK_TULIP(7, "RED_ROSE"), + PINK_WALL_BANNER(9, "WALL_BANNER"), + PINK_WOOL(6, "WOOL"), + PISTON("PISTON_BASE"), + PISTON_HEAD("PISTON_EXTENSION"), + PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"), + PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"), + PODZOL(2, "DIRT"), + POISONOUS_POTATO, + POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"), + POLISHED_ANDESITE(6, "STONE"), + POLISHED_ANDESITE_SLAB, + POLISHED_ANDESITE_STAIRS, + POLISHED_BASALT("1.16"), + POLISHED_BLACKSTONE("1.16"), + POLISHED_BLACKSTONE_BRICKS("1.16"), + POLISHED_BLACKSTONE_BRICK_SLAB("1.16"), + POLISHED_BLACKSTONE_BRICK_STAIRS("1.16"), + POLISHED_BLACKSTONE_BRICK_WALL("1.16"), + POLISHED_BLACKSTONE_BUTTON("1.16"), + POLISHED_BLACKSTONE_PRESSURE_PLATE("1.16"), + POLISHED_BLACKSTONE_SLAB("1.16"), + POLISHED_BLACKSTONE_STAIRS("1.16"), + POLISHED_BLACKSTONE_WALL("1.16"), + POLISHED_DIORITE(4, "STONE"), + POLISHED_DIORITE_SLAB, + POLISHED_DIORITE_STAIRS, + POLISHED_GRANITE(2, "STONE"), + POLISHED_GRANITE_SLAB, + POLISHED_GRANITE_STAIRS, + POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"), + POPPY("RED_ROSE"), + PORKCHOP("PORK"), + POTATO("POTATO_ITEM"), + POTATOES("POTATO"), + POTION, + POTTED_ACACIA_SAPLING(4, "SAPLING", "FLOWER_POT"), + POTTED_ALLIUM(2, "RED_ROSE", "FLOWER_POT"), + POTTED_AZURE_BLUET(3, "RED_ROSE", "FLOWER_POT"), + POTTED_BAMBOO, + POTTED_BIRCH_SAPLING(2, "SAPLING", "FLOWER_POT"), + POTTED_BLUE_ORCHID(1, "RED_ROSE", "FLOWER_POT"), + POTTED_BROWN_MUSHROOM("FLOWER_POT"), + POTTED_CACTUS("FLOWER_POT"), + POTTED_CORNFLOWER, + POTTED_CRIMSON_FUNGUS("1.16"), + POTTED_CRIMSON_ROOTS("1.16"), + POTTED_DANDELION("YELLOW_FLOWER", "FLOWER_POT"), + POTTED_DARK_OAK_SAPLING(5, "SAPLING", "FLOWER_POT"), + POTTED_DEAD_BUSH("FLOWER_POT"), + POTTED_FERN(2, "LONG_GRASS", "FLOWER_POT"), + POTTED_JUNGLE_SAPLING(3, "SAPLING", "FLOWER_POT"), + POTTED_LILY_OF_THE_VALLEY, + POTTED_OAK_SAPLING("SAPLING", "FLOWER_POT"), + POTTED_ORANGE_TULIP(5, "RED_ROSE", "FLOWER_POT"), + POTTED_OXEYE_DAISY(8, "RED_ROSE", "FLOWER_POT"), + POTTED_PINK_TULIP(7, "RED_ROSE", "FLOWER_POT"), + POTTED_POPPY("RED_ROSE", "FLOWER_POT"), + POTTED_RED_MUSHROOM("FLOWER_POT"), + POTTED_RED_TULIP(4, "RED_ROSE", "FLOWER_POT"), + POTTED_SPRUCE_SAPLING(1, "SAPLING", "FLOWER_POT"), + POTTED_WARPED_FUNGUS("1.16"), + POTTED_WARPED_ROOTS("1.16"), + POTTED_WHITE_TULIP(6, "RED_ROSE", "FLOWER_POT"), + POTTED_WITHER_ROSE, + POWERED_RAIL, + PRISMARINE, + PRISMARINE_BRICKS(2, "PRISMARINE"), + PRISMARINE_BRICK_SLAB(4, "STEP"), + PRISMARINE_BRICK_STAIRS("1.13"), + PRISMARINE_CRYSTALS, + PRISMARINE_SHARD, + PRISMARINE_SLAB("1.13"), + PRISMARINE_STAIRS("1.13"), + PRISMARINE_WALL, + PUFFERFISH(3, "RAW_FISH"), + PUFFERFISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + PUFFERFISH_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + PUMPKIN, + PUMPKIN_PIE, + PUMPKIN_SEEDS, + PUMPKIN_STEM, + PURPLE_BANNER(5, "STANDING_BANNER", "BANNER"), + PURPLE_BED(10, "BED_BLOCK", "BED"), + PURPLE_CARPET(10, "CARPET"), + PURPLE_CONCRETE(10, "CONCRETE"), + PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"), + PURPLE_DYE(5, "INK_SACK"), + PURPLE_GLAZED_TERRACOTTA(10, "1.12", "HARD_CLAY", "STAINED_CLAY", "PURPLE_TERRACOTTA"), + PURPLE_SHULKER_BOX, + PURPLE_STAINED_GLASS(10, "STAINED_GLASS"), + PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"), + PURPLE_TERRACOTTA(10, "HARD_CLAY", "STAINED_CLAY"), + PURPLE_WALL_BANNER(5, "WALL_BANNER"), + PURPLE_WOOL(10, "WOOL"), + PURPUR_BLOCK, + PURPUR_PILLAR, + PURPUR_SLAB("PURPUR_DOUBLE_SLAB"), + PURPUR_STAIRS, + QUARTZ, + QUARTZ_BLOCK, + QUARTZ_BRICKS("1.16"), + QUARTZ_PILLAR(2, "QUARTZ_BLOCK"), + QUARTZ_SLAB(7, "STEP"), + QUARTZ_STAIRS, + RABBIT, + RABBIT_FOOT, + RABBIT_HIDE, + RABBIT_SPAWN_EGG(101, "MONSTER_EGG"), + RABBIT_STEW, + RAIL("RAILS"), + RAVAGER_SPAWN_EGG("1.14"), + REDSTONE, + REDSTONE_BLOCK, + /** + * Unlike redstone torch, REDSTONE_LAMP_ON isn't an item. + * The name is just here on the list for matching. + * + * @see #REDSTONE_TORCH + */ + REDSTONE_LAMP("REDSTONE_LAMP_ON", "REDSTONE_LAMP_OFF"), + REDSTONE_ORE("GLOWING_REDSTONE_ORE"), + /** + * REDSTONE_TORCH_OFF isn't an item, but a block. + * But REDSTONE_TORCH_ON is the item. + * The name is just here on the list for matching. + */ + REDSTONE_TORCH("REDSTONE_TORCH_OFF", "REDSTONE_TORCH_ON"), + REDSTONE_WALL_TORCH, + REDSTONE_WIRE, + RED_BANNER(1, "STANDING_BANNER", "BANNER"), + /** + * Data value 14 or 0 + */ + RED_BED(0, "BED_BLOCK", "BED"), + RED_CARPET(14, "CARPET"), + RED_CONCRETE(14, "CONCRETE"), + RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"), + RED_DYE(1, "INK_SACK", "ROSE_RED"), + RED_GLAZED_TERRACOTTA(14, "1.12", "HARD_CLAY", "STAINED_CLAY", "RED_TERRACOTTA"), + RED_MUSHROOM, + RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"), + RED_NETHER_BRICKS("RED_NETHER_BRICK"), + RED_NETHER_BRICK_SLAB(4, "STEP"), + RED_NETHER_BRICK_STAIRS, + RED_NETHER_BRICK_WALL, + RED_SAND(1, "SAND"), + RED_SANDSTONE, + RED_SANDSTONE_SLAB("STONE_SLAB2", "DOUBLE_STONE_SLAB2"), + RED_SANDSTONE_STAIRS, + RED_SANDSTONE_WALL, + RED_SHULKER_BOX, + RED_STAINED_GLASS(14, "STAINED_GLASS"), + RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"), + RED_TERRACOTTA(14, "HARD_CLAY", "STAINED_CLAY"), + RED_TULIP(4, "RED_ROSE"), + RED_WALL_BANNER(1, "WALL_BANNER"), + RED_WOOL(14, "WOOL"), + REPEATER("DIODE_BLOCK_ON", "DIODE_BLOCK_OFF", "DIODE"), + REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"), + RESPAWN_ANCHOR("1.16"), + ROSE_BUSH(4, "DOUBLE_PLANT"), + ROTTEN_FLESH, + SADDLE, + SALMON(1, "RAW_FISH"), + SALMON_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + SALMON_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + SAND, + SANDSTONE, + SANDSTONE_SLAB(1, "STEP", "STONE_SLAB", "DOUBLE_STEP"), + SANDSTONE_STAIRS, + SANDSTONE_WALL, + SCAFFOLDING("1.14", "SLIME_BLOCK", ""), + SCUTE("1.13"), + SEAGRASS("1.13", "GRASS", ""), + SEA_LANTERN, + SEA_PICKLE("1.13"), + SHEARS, + SHEEP_SPAWN_EGG(91, "MONSTER_EGG"), + SHIELD, + SHROOMLIGHT("1.16"), + SHULKER_BOX("PURPLE_SHULKER_BOX"), + SHULKER_SHELL, + SHULKER_SPAWN_EGG(69, "MONSTER_EGG"), + SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"), + SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"), + SKELETON_SKULL("SKULL", "SKULL_ITEM"), + SKELETON_SPAWN_EGG(51, "MONSTER_EGG"), + SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"), + SKULL_BANNER_PATTERN, + SLIME_BALL, + SLIME_BLOCK, + SLIME_SPAWN_EGG(55, "MONSTER_EGG"), + SMITHING_TABLE, + SMOKER("1.14", "FURNACE", ""), + SMOOTH_QUARTZ("1.13", "QUARTZ", ""), + SMOOTH_QUARTZ_SLAB(7, "STEP"), + SMOOTH_QUARTZ_STAIRS, + SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"), + SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"), + SMOOTH_RED_SANDSTONE_STAIRS, + SMOOTH_SANDSTONE(2, "SANDSTONE"), + SMOOTH_SANDSTONE_SLAB("STEP"), + SMOOTH_SANDSTONE_STAIRS, + SMOOTH_STONE("STEP"), + SMOOTH_STONE_SLAB("STEP"), + SNOW, + SNOWBALL("SNOW_BALL"), + SNOW_BLOCK, + SOUL_CAMPFIRE("1.16"), + SOUL_FIRE("1.16"), + SOUL_LANTERN("1.16"), + SOUL_SAND, + SOUL_SOIL("1.16"), + SOUL_TORCH("1.16"), + SOUL_WALL_TORCH("1.16"), + SPAWNER("MOB_SPAWNER"), + SPECTRAL_ARROW("1.9", "ARROW", ""), + SPIDER_EYE, + SPIDER_SPAWN_EGG(52, "MONSTER_EGG"), + SPLASH_POTION, + SPONGE, + SPRUCE_BOAT("BOAT_SPRUCE"), + SPRUCE_BUTTON("WOOD_BUTTON"), + SPRUCE_DOOR("SPRUCE_DOOR_ITEM", "SPRUCE_DOOR"), + SPRUCE_FENCE, + SPRUCE_FENCE_GATE, + SPRUCE_LEAVES(1, "LEAVES", "LEAVES_2"), + SPRUCE_LOG(1, "LOG"), + SPRUCE_PLANKS(1, "WOOD"), + SPRUCE_PRESSURE_PLATE("WOOD_PLATE"), + SPRUCE_SAPLING(1, "SAPLING"), + SPRUCE_SIGN("SIGN_POST", "SIGN"), + SPRUCE_SLAB(1, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"), + SPRUCE_TRAPDOOR("TRAP_DOOR"), + SPRUCE_WALL_SIGN("WALL_SIGN"), + SPRUCE_WOOD(1, "LOG"), + SQUID_SPAWN_EGG(94, "MONSTER_EGG"), + STICK, + STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"), + STONE, + STONECUTTER("1.14"), + STONE_AXE, + STONE_BRICKS("SMOOTH_BRICK"), + STONE_BRICK_SLAB(4, "STEP", "STONE_SLAB", "DOUBLE_STEP"), + STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + STONE_BRICK_WALL, + STONE_BUTTON, + STONE_HOE, + STONE_PICKAXE, + STONE_PRESSURE_PLATE("STONE_PLATE"), + STONE_SHOVEL("STONE_SPADE"), + STONE_SLAB("STEP", "DOUBLE_STEP"), + STONE_STAIRS, + STONE_SWORD, + STRAY_SPAWN_EGG(6, "MONSTER_EGG"), + STRIDER_SPAWN_EGG("1.16"), + STRING, + STRIPPED_ACACIA_LOG("LOG_2"), + STRIPPED_ACACIA_WOOD("LOG_2"), + STRIPPED_BIRCH_LOG(2, "LOG"), + STRIPPED_BIRCH_WOOD(2, "LOG"), + STRIPPED_CRIMSON_HYPHAE("1.16"), + STRIPPED_CRIMSON_STEM("1.16"), + STRIPPED_DARK_OAK_LOG("LOG"), + STRIPPED_DARK_OAK_WOOD("LOG"), + STRIPPED_JUNGLE_LOG(3, "LOG"), + STRIPPED_JUNGLE_WOOD(3, "LOG"), + STRIPPED_OAK_LOG("LOG"), + STRIPPED_OAK_WOOD("LOG"), + STRIPPED_SPRUCE_LOG(1, "LOG"), + STRIPPED_SPRUCE_WOOD(1, "LOG"), + STRIPPED_WARPED_HYPHAE("1.16"), + STRIPPED_WARPED_STEM("1.16"), + STRUCTURE_BLOCK, + /** + * Originally developers used barrier blocks for its purpose. + * So technically this isn't really considered as a suggested material. + */ + STRUCTURE_VOID("1.10", "", "BARRIER"), + SUGAR, + /** + * Sugar Cane is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + SUGAR_CANE("SUGAR_CANE_BLOCK"), + SUNFLOWER("DOUBLE_PLANT"), + SUSPICIOUS_STEW("1.14", "MUSHROOM_STEW", ""), + SWEET_BERRIES("1.14"), + SWEET_BERRY_BUSH("1.14", "GRASS", ""), + TALL_GRASS(2, "DOUBLE_PLANT"), + TALL_SEAGRASS(2, "1.13", "TALL_GRASS", ""), + TARGET("1.16"), + TERRACOTTA("HARD_CLAY"), + TIPPED_ARROW("1.9", "ARROW", ""), + TNT, + TNT_MINECART("EXPLOSIVE_MINECART"), + TORCH, + TOTEM_OF_UNDYING("TOTEM"), + TRADER_LLAMA_SPAWN_EGG(103, "1.14", "MONSTER_EGG", ""), + TRAPPED_CHEST, + TRIDENT("1.13"), + TRIPWIRE, + TRIPWIRE_HOOK, + TROPICAL_FISH(2, "RAW_FISH"), + TROPICAL_FISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET"), + TROPICAL_FISH_SPAWN_EGG("1.13", "MONSTER_EGG"), + TUBE_CORAL("1.13"), + TUBE_CORAL_BLOCK("1.13"), + TUBE_CORAL_FAN("1.13"), + TUBE_CORAL_WALL_FAN, + TURTLE_EGG("1.13", "EGG", ""), + TURTLE_HELMET("1.13", "IRON_HELMET", ""), + TURTLE_SPAWN_EGG("1.13", "CHICKEN_SPAWN_EGG", ""), + TWISTING_VINES("1.16"), + TWISTING_VINES_PLANT("1.16"), + VEX_SPAWN_EGG(35, "MONSTER_EGG"), + VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"), + VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"), + VINE, + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #CAVE_AIR + */ + VOID_AIR("AIR"), + WALL_TORCH("TORCH"), + WANDERING_TRADER_SPAWN_EGG("1.14", "VILLAGER_SPAWN_EGG", ""), + WARPED_BUTTON("1.16"), + WARPED_DOOR("1.16"), + WARPED_FENCE("1.16"), + WARPED_FENCE_GATE("1.16"), + WARPED_FUNGUS("1.16"), + WARPED_FUNGUS_ON_A_STICK("1.16"), + WARPED_HYPHAE("1.16"), + WARPED_NYLIUM("1.16"), + WARPED_PLANKS("1.16"), + WARPED_PRESSURE_PLATE("1.16"), + WARPED_ROOTS("1.16"), + WARPED_SIGN("1.16", "SIGN_POST"), + WARPED_SLAB("1.16"), + WARPED_STAIRS("1.16"), + WARPED_STEM("1.16"), + WARPED_TRAPDOOR("1.16"), + WARPED_WALL_SIGN("1.16", "WALL_SIGN"), + WARPED_WART_BLOCK("1.16"), + /** + * This is used for blocks only. + * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. + * After 1.13+ this uses + * https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/Levelled.html water flowing system. + * Use XBlock for this instead. + */ + WATER("STATIONARY_WATER"), + WATER_BUCKET, + WEEPING_VINES("1.16"), + WEEPING_VINES_PLANT("1.16"), + WET_SPONGE(1, "SPONGE"), + /** + * Wheat is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + WHEAT("CROPS"), + WHEAT_SEEDS("SEEDS"), + WHITE_BANNER(15, "STANDING_BANNER", "BANNER"), + WHITE_BED("BED_BLOCK", "BED"), + WHITE_CARPET("CARPET"), + WHITE_CONCRETE("CONCRETE"), + WHITE_CONCRETE_POWDER("CONCRETE_POWDER"), + WHITE_DYE(15, "1.14", "INK_SACK", "BONE_MEAL"), + WHITE_GLAZED_TERRACOTTA("1.12", "HARD_CLAY", "STAINED_CLAY"), + WHITE_SHULKER_BOX, + WHITE_STAINED_GLASS("STAINED_GLASS"), + WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"), + WHITE_TERRACOTTA("HARD_CLAY", "STAINED_CLAY", "TERRACOTTA"), + WHITE_TULIP(6, "RED_ROSE"), + WHITE_WALL_BANNER(15, "WALL_BANNER"), + WHITE_WOOL("WOOL"), + WITCH_SPAWN_EGG(66, "MONSTER_EGG"), + WITHER_ROSE("1.14", "BLACK_DYE", ""), + WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"), + WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"), + WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"), + WOLF_SPAWN_EGG(95, "MONSTER_EGG"), + WOODEN_AXE("WOOD_AXE"), + WOODEN_HOE("WOOD_HOE"), + WOODEN_PICKAXE("WOOD_PICKAXE"), + WOODEN_SHOVEL("WOOD_SPADE"), + WOODEN_SWORD("WOOD_SWORD"), + WRITABLE_BOOK("BOOK_AND_QUILL"), + WRITTEN_BOOK, + YELLOW_BANNER(11, "STANDING_BANNER", "BANNER"), + YELLOW_BED(4, "BED_BLOCK", "BED"), + YELLOW_CARPET(4, "CARPET"), + YELLOW_CONCRETE(4, "CONCRETE"), + YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"), + YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"), + YELLOW_GLAZED_TERRACOTTA(4, "1.12", "HARD_CLAY", "STAINED_CLAY", "YELLOW_TERRACOTTA"), + YELLOW_SHULKER_BOX, + YELLOW_STAINED_GLASS(4, "STAINED_GLASS"), + YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"), + YELLOW_TERRACOTTA(4, "HARD_CLAY", "STAINED_CLAY"), + YELLOW_WALL_BANNER(11, "WALL_BANNER"), + YELLOW_WOOL(4, "WOOL"), + ZOGLIN_SPAWN_EGG("1.16"), + ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"), + ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"), + ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"), + ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIFIED_PIGLIN_SPAWN_EGG(57, "MONSTER_EGG", "ZOMBIE_PIGMAN_SPAWN_EGG"); + + + /** + * Cached set of {@link XMaterial#values()} to avoid allocating memory for + * calling the method every time. + * This list is unmodifiable. + * + * @since 2.0.0 + */ + public static final List VALUES = Collections.unmodifiableList(Arrays.asList(values())); + + /** + * We don't want to use {@link Enums#getIfPresent(Class, String)} to avoid a few checks. + * + * @since 5.1.0 + */ + private static final Map NAMES = new HashMap<>(); + + /** + * A set of material names that can be damaged. + *

+ * Most of the names are not complete as this list is intended to be + * checked with {@link String#contains} for memory usage. + * + * @since 1.0.0 + */ + private static final Set DAMAGEABLE = new HashSet<>(Arrays.asList( + "HELMET", "CHESTPLATE", "LEGGINGS", "BOOTS", + "SWORD", "AXE", "PICKAXE", "SHOVEL", "HOE", + "ELYTRA", "TRIDENT", "HORSE_ARMOR", "BARDING", + "SHEARS", "FLINT_AND_STEEL", "BOW", "FISHING_ROD", + "CARROT_ON_A_STICK", "CARROT_STICK", "SPADE", "SHIELD" + )); + /** + * XMaterial Paradox (Duplication Check) + *

+ * A map of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. + * Values are the new material names. + *
+ * Duplicates are normally only checked by keys, not values. + * + * @since 3.0.0 + */ + private static final EnumMap DUPLICATED = new EnumMap(XMaterial.class); + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For strings that match a certain XMaterial. Mostly cached for configs. + * + * @since 1.0.0 + */ + private static final Cache NAME_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(15, TimeUnit.MINUTES) + .build(); + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For XMaterials that are already parsed once. + * + * @since 3.0.0 + */ + private static final Cache> PARSED_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + /* + * A set of all the legacy names without duplicates. + *

+ * It'll help to free up a lot of memory if it's not used. + * Add it back if you need it. + * + * @see #containsLegacy(String) + * @since 2.2.0 + * + private static final ImmutableSet LEGACY_VALUES = VALUES.stream().map(XMaterial::getLegacy) + .flatMap(Arrays::stream) + .filter(m -> m.charAt(1) == '.') + .collect(Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf)); + */ + /** + * This is used for {@link #isOneOf(Collection)} + * + * @since 3.4.0 + */ + private static final LoadingCache CACHED_REGEX = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(new CacheLoader() { + @Override + public Pattern load(@Nonnull String str) { + try { + return Pattern.compile(str); + } catch (PatternSyntaxException ex) { + ex.printStackTrace(); + return null; + } + } + }); + /** + * The current version of the server in the a form of a major version. + * + * @since 1.0.0 + */ + private static final int VERSION = Integer.parseInt(getMajorVersion(Bukkit.getVersion()).substring(2)); + /** + * Cached result if the server version is after the v1.13 flattening update. + * Please don't mistake this with flat-chested people. It happened. + * + * @since 3.0.0 + */ + private static final boolean ISFLAT = supports(13); + + static { + DUPLICATED.put(MELON, MELON_SLICE); + DUPLICATED.put(CARROT, CARROTS); + DUPLICATED.put(POTATO, POTATOES); + DUPLICATED.put(BEETROOT, BEETROOTS); + DUPLICATED.put(BROWN_MUSHROOM, BROWN_MUSHROOM_BLOCK); + DUPLICATED.put(BRICK, BRICKS); + DUPLICATED.put(NETHER_BRICK, NETHER_BRICKS); + + // Illegal Elements + // Since both 1.12 and 1.13 have _DOOR XMaterial will use it + // for 1.12 to parse the material, but it needs _DOOR_ITEM. + // We'll trick XMaterial into thinking this needs to be parsed + // using the old methods. + // Some of these materials have their enum name added to the legacy list as well. + DUPLICATED.put(DARK_OAK_DOOR, DARK_OAK_DOOR); + DUPLICATED.put(ACACIA_DOOR, ACACIA_DOOR); + DUPLICATED.put(BIRCH_DOOR, BIRCH_DOOR); + DUPLICATED.put(JUNGLE_DOOR, JUNGLE_DOOR); + DUPLICATED.put(SPRUCE_DOOR, SPRUCE_DOOR); + DUPLICATED.put(CAULDRON, CAULDRON); + DUPLICATED.put(BREWING_STAND, BREWING_STAND); + DUPLICATED.put(FLOWER_POT, FLOWER_POT); + } + + static { + for (XMaterial material : VALUES) NAMES.put(material.name(), material); + } + + /** + * The data value of this material https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening + * + * @see #getData() + */ + private final byte data; + /** + * A list of material names that was being used for older verions. + * + * @see #getLegacy() + */ + private final String[] legacy; + + XMaterial(int data, String... legacy) { + this.data = (byte) data; + this.legacy = legacy; + } + + XMaterial() { + this(0); + } + + XMaterial(String... legacy) { + this(0, legacy); + } + + /** + * Checks if the version is 1.13 Aquatic Update or higher. + * An invocation of this method yields the cached result from the expression: + *

+ *

+ * {@link #supports(int) 13}} + *
+ * + * @return true if 1.13 or higher. + * @see #getVersion() + * @see #supports(int) + * @since 1.0.0 + */ + public static boolean isNewVersion() { + return ISFLAT; + } + + /** + * This is just an extra method that method that can be used for many cases. + * It can be used in {@link org.bukkit.event.player.PlayerInteractEvent} + * or when accessing {@link org.bukkit.entity.Player#getMainHand()}, + * or other compatibility related methods. + *

+ * An invocation of this method yields exactly the same result as the expression: + *

+ *

+ * !{@link #supports(int)} 9 + *
+ * + * @since 2.0.0 + */ + public static boolean isOneEight() { + return !supports(9); + } + + /** + * Gets the {@link XMaterial} with this name without throwing an exception. + * + * @param name the name of the material. + * @return an optional that can be empty. + * @since 5.1.0 + */ + @Nonnull + private static Optional getIfPresent(@Nonnull String name) { + return Optional.ofNullable(NAMES.get(name)); + } + + /** + * The current version of the server. + * + * @return the current server version or 0.0 if unknown. + * @see #isNewVersion() + * @since 2.0.0 + */ + public static double getVersion() { + return VERSION; + } + + /** + * When using newer versions of Minecraft ({@link #isNewVersion()}), helps + * to find the old material name with its data value using a cached search for optimization. + * + * @see #matchDefinedXMaterial(String, byte) + * @since 1.0.0 + */ + @Nullable + private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { + String holder = name + data; + XMaterial cache = NAME_CACHE.getIfPresent(holder); + if (cache != null) return cache; + + for (XMaterial material : VALUES) { + // Not using material.name().equals(name) check is intended. + if ((data == -1 || data == material.data) && material.anyMatchLegacy(name)) { + NAME_CACHE.put(holder, material); + return material; + } + } + + return null; + } + + /** + * Checks if XMaterial enum contains a material with the given name. + *

+ * You should use {@link #matchXMaterial(String)} instead if you're going + * to get the XMaterial object after checking if it's available in the list + * by doing a simple {@link Optional#isPresent()} check. + * This is just to avoid multiple loops for maximum performance. + * + * @param name name of the material. + * @return true if XMaterial enum has this material. + * @since 1.0.0 + */ + public static boolean contains(@Nonnull String name) { + Validate.notEmpty(name, "Cannot check for null or empty material name"); + name = format(name); + + for (XMaterial materials : VALUES) { + if (materials.name().equals(name)) return true; + } + return false; + } + + /** + * Parses the given material name as an XMaterial with unspecified data value. + * + * @see #matchXMaterialWithData(String) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(@Nonnull String name) { + Validate.notEmpty(name, "Cannot match a material with null or empty material name"); + Optional oldMatch = matchXMaterialWithData(name); + if (oldMatch.isPresent()) return oldMatch; + return matchDefinedXMaterial(format(name), (byte) -1); + } + + /** + * Parses material name and data value from the specified string. + * The separator for the material name and its data value is {@code :} + * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. + *

+ * Examples + *

+     *     {@code INK_SACK:1 -> RED_DYE}
+     *     {@code WOOL: 14  -> RED_WOOL}
+     * 
+ * + * @param name the material string that consists of the material name, data and separator character. + * @return the parsed XMaterial. + * @see #matchXMaterial(String) + * @since 3.0.0 + */ + @Nonnull + private static Optional matchXMaterialWithData(String name) { + int index = name.indexOf(':'); + if (index != -1) { + String mat = format(name.substring(0, index)); + + try { + byte data = (byte) Integer.parseInt(StringUtils.deleteWhitespace(name.substring(index + 1))); + return matchDefinedXMaterial(mat, data); + } catch (NumberFormatException ignored) { + } + } + + return Optional.empty(); + } + + /** + * Parses the given material as an XMaterial. + * + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static XMaterial matchXMaterial(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot match null material"); + return matchDefinedXMaterial(material.name(), (byte) -1) + .orElseThrow(() -> new IllegalArgumentException("Unsupported material with no data value: " + material.name())); + } + + /** + * Parses the given item as an XMaterial using its material and data value (durability). + * + * @param item the ItemStack to match. + * @return an XMaterial if matched any. + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @since 2.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public static XMaterial matchXMaterial(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot match null ItemStack"); + String material = item.getType().name(); + byte data = (byte) (ISFLAT || isDamageable(material) ? 0 : item.getDurability()); + + return matchDefinedXMaterial(material, data) + .orElseThrow(() -> new IllegalArgumentException("Unsupported material: " + material + " (" + data + ')')); + } + + /** + * Parses the given material name and data value as an XMaterial. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the formatted name of the material. + * @param data the data value of the material. + * @return an XMaterial (with the same data value if specified) + * @see #matchXMaterial(Material) + * @see #matchXMaterial(int, byte) + * @see #matchXMaterial(ItemStack) + * @since 3.0.0 + */ + @SuppressWarnings("OptionalAssignedToNull") + @Nonnull + private static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { + boolean duplicated = isDuplicated(name); + + // Do basic number and boolean checks before accessing more complex enum stuff. + // Maybe we can simplify (ISFLAT || !duplicated) with the (!ISFLAT && duplicated) under it to save a few nanoseconds? + // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; + Optional xMaterial = null; + if (data <= 0 && (ISFLAT || !duplicated)) { + // Apparently the transform method is more efficient than toJavaUtil() + // toJavaUtil isn't even supported in older versions. + xMaterial = getIfPresent(name); + if (xMaterial.isPresent()) return xMaterial; + } + + // XMaterial Paradox (Duplication Check) + // I've concluded that this is just an infinite loop that keeps + // going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. + // This solution works just fine anyway. + XMaterial xMat = requestOldXMaterial(name, data); + if (xMat == null) { + // Special case. Refer to FILLED_MAP for more info. + if (data > 0 && name.endsWith("MAP")) return Optional.of(FILLED_MAP); + return Optional.empty(); + } + + if (!ISFLAT && duplicated && xMat.name().charAt(xMat.name().length() - 1) == 'S') { + // A solution for XMaterial Paradox. + // Manually parses the duplicated materials to find the exact material based on the server version. + // If ends with "S" -> Plural Form Material + return xMaterial == null ? getIfPresent(name) : xMaterial; + } + return Optional.ofNullable(xMat); + } + + /** + * XMaterial Paradox (Duplication Check) + * Checks if the material has any duplicates. + *

+ * Example: + *

{@code MELON, CARROT, POTATO, BEETROOT -> true} + * + * @param name the name of the material to check. + * @return true if there's a duplicated material for this material, otherwise false. + * @see #isDuplicated() + * @since 2.0.0 + */ + private static boolean isDuplicated(@Nonnull String name) { + // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. + for (Map.Entry duplicated : DUPLICATED.entrySet()) { + XMaterial material = duplicated.getKey(); + if (material.name().equals(name) || material.anyMatchLegacy(name)) return true; + } + return false; + } + + /** + * Gets the XMaterial based on the material's ID (Magic Value) and data value.
+ * You should avoid using this for performance issues. + * + * @param id the ID (Magic value) of the material. + * @param data the data value of the material. + * @return a parsed XMaterial with the same ID and data value. + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(int id, byte data) { + if (id < 0 || data < 0) return Optional.empty(); + + // Looping through Material.values() will take longer. + for (XMaterial materials : VALUES) + if (materials.data == data && materials.getId() == id) return Optional.of(materials); + return Optional.empty(); + } + + /** + * Attempts to build the string like an enum name. + * Removes all the spaces, and extra non-English characters. Also removes some config/in-game based strings. + * While this method is hard to maintain, it's extremely efficient. It's approximately more than x5 times faster than + * the normal RegEx + String Methods approach for both formatted and unformatted material names. + * + * @param name the material name to modify. + * @return a Material enum name. + * @since 2.0.0 + */ + @Nonnull + protected static String format(@Nonnull String name) { + int len = name.length(); + char[] chs = new char[len]; + int count = 0; + boolean appendUnderline = false; + + for (int i = 0; i < len; ++i) { + char ch = name.charAt(i); + + if (!appendUnderline && count != 0 && (ch == '-' || ch == ' ' || ch == '_') && chs[count] != '_') appendUnderline = true; + else { + boolean number = false; + // Old materials have numbers in them. + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (number = (ch >= '0' && ch <= '9'))) { + if (appendUnderline) { + chs[count++] = '_'; + appendUnderline = false; + } + + if (number) chs[count++] = ch; + else chs[count++] = (char) (ch & 0x5f); + } + } + } + + return new String(chs, 0, count); + } + + /** + * Checks if the specified version is the same version or higher than the current server version. + * + * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 + * @return true of the version is equal or higher than the current version. + * @since 2.0.0 + */ + public static boolean supports(int version) { + return VERSION >= version; + } + + /** + * Converts the enum names to a more friendly and readable string. + * + * @return a formatted string. + * @see #toWord(String) + * @since 2.1.0 + */ + @Nonnull + public static String toWord(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot translate a null material to a word"); + return toWord(material.name()); + } + + /** + * Parses an enum name to a normal word. + * Normal names have underlines removed and each word capitalized. + *

+ * Examples: + *

+     *     EMERALD                 -> Emerald
+     *     EMERALD_BLOCK           -> Emerald Block
+     *     ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple
+     * 
+ * + * @param name the name of the enum. + * @return a cleaned more readable enum name. + * @since 2.1.0 + */ + @Nonnull + private static String toWord(@Nonnull String name) { + return WordUtils.capitalize(name.replace('_', ' ').toLowerCase(Locale.ENGLISH)); + } + + /** + * Gets the exact major version (..., 1.9, 1.10, ..., 1.14) + * + * @param version Supports {@link Bukkit#getVersion()}, {@link Bukkit#getBukkitVersion()} and normal formats such as "1.14" + * @return the exact major version. + * @since 2.0.0 + */ + @Nonnull + public static String getMajorVersion(@Nonnull String version) { + Validate.notEmpty(version, "Cannot get major Minecraft version from null or empty string"); + + // getVersion() + int index = version.lastIndexOf("MC:"); + if (index != -1) { + version = version.substring(index + 4, version.length() - 1); + } else if (version.endsWith("SNAPSHOT")) { + // getBukkitVersion() + index = version.indexOf('-'); + version = version.substring(0, index); + } + + // 1.13.2, 1.14.4, etc... + int lastDot = version.lastIndexOf('.'); + if (version.indexOf('.') != lastDot) version = version.substring(0, lastDot); + + return version; + } + + /** + * Checks if the material can be damaged by using it. + * Names going through this method are not formatted. + * + * @param name the name of the material. + * @return true of the material can be damaged. + * @see #isDamageable() + * @since 1.0.0 + */ + public static boolean isDamageable(@Nonnull String name) { + Objects.requireNonNull(name, "Material name cannot be null"); + for (String damageable : DAMAGEABLE) { + if (name.contains(damageable)) return true; + } + return false; + } + + /** + * Checks if the list of given material names matches the given base material. + * Mostly used for configs. + *

+ * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. + *

+ * Example: + *

+     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
+     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
+     * 
+ *
+ * {@code CONTAINS} Examples: + *
+     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
+     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
+     * 
+ *

+ * {@code REGEX} Examples + *

+     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
+     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
+     * 
+ *

+ * The reason that there are tags for {@code CONTAINS} and {@code REGEX} + * is for the performance. + * Please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag. + * It'll have a huge impact on performance. + * Please avoid using {@code (capturing groups)} there's no use for them in this case. + * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. + *

+ * You can make a cache for pre-compiled RegEx patterns from your config. + * It's better, but not much faster since these patterns are not that complex. + *

+ * Want to learn RegEx? You can mess around in RegExr website. + * + * @param materials the material names to check base material on. + * @return true if one of the given material names is similar to the base material. + * @since 3.1.1 + */ + public boolean isOneOf(@Nullable Collection materials) { + if (materials == null || materials.isEmpty()) return false; + String name = this.name(); + + for (String comp : materials) { + String checker = comp.toUpperCase(Locale.ENGLISH); + if (checker.startsWith("CONTAINS:")) { + comp = format(checker.substring(9)); + if (name.contains(comp)) return true; + continue; + } + if (checker.startsWith("REGEX:")) { + comp = comp.substring(6); + Pattern pattern = CACHED_REGEX.getUnchecked(comp); + if (pattern != null && pattern.matcher(name).matches()) return true; + continue; + } + + // Direct Object Equals + Optional xMat = matchXMaterial(comp); + if (xMat.isPresent() && xMat.get() == this) return true; + } + return false; + } + + /** + * Gets the version which this material was added in. + * If the material doesn't have a version it'll return 0; + * + * @return the Minecraft version which tihs material was added in. + * @since 3.0.0 + */ + public int getMaterialVersion() { + if (this.legacy.length == 0) return 0; + String version = this.legacy[0]; + if (version.charAt(1) != '.') return 0; + + return Integer.parseInt(version.substring(2)); + } + + /** + * Sets the {@link Material} (and data value on older versions) of an item. + * Damageable materials will not have their durability changed. + *

+ * Use {@link #parseItem()} instead when creating new ItemStacks. + * + * @param item the item to change its type. + * @see #parseItem() + * @since 3.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public ItemStack setType(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot set material for null ItemStack"); + Material material = this.parseMaterial(); + Objects.requireNonNull(material, "Unsupported material: " + this.name()); + + item.setType(material); + if (!ISFLAT && !this.isDamageable()) item.setDurability(this.data); + return item; + } + + /** + * Checks if the given string matches any of this material's legacy material names. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the name to check + * @return true if it's one of the legacy names. + * @since 2.0.0 + */ + private boolean anyMatchLegacy(@Nonnull String name) { + for (String legacy : this.legacy) { + if (legacy.isEmpty()) break; // Left-side suggestion list + if (name.equals(legacy)) return true; + } + return false; + } + + /** + * User-friendly readable name for this material + * In most cases you should be using {@link #name()} instead. + * + * @return string of this object. + * @see #toWord(String) + * @since 3.0.0 + */ + @Override + public String toString() { + return toWord(this.name()); + } + + /** + * Gets the ID (Magic value) of the material. + * + * @return the ID of the material or -1 if it's a new block or the material is not supported. + * @see #matchXMaterial(int, byte) + * @since 2.2.0 + */ + @SuppressWarnings("deprecation") + public int getId() { + if (this.data != 0 || (this.legacy.length != 0 && this.legacy[0].charAt(1) == '.' && Integer.parseInt(this.legacy[0].substring(2)) >= 13)) return -1; + Material material = this.parseMaterial(); + return material == null ? -1 : material.getId(); + } + + /** + * Checks if the material has any duplicates. + * + * @return true if there is a duplicated name for this material, otherwise false. + * @see #isDuplicated(String) + * @since 2.0.0 + */ + public boolean isDuplicated() { + return DUPLICATED.containsKey(this); + } + + /** + * Checks if the material can be damaged by using it. + * Names going through this method are not formatted. + * + * @return true if the item can be damaged (have its durability changed), otherwise false. + * @see #isDamageable(String) + * @since 1.0.0 + */ + public boolean isDamageable() { + return isDamageable(this.name()); + } + + /** + * The data value of this material pre-flattening. + *

+ * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} + * or {@link ItemStack#getDurability()} if not damageable. + * + * @return data of this material, or 0 if none. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public byte getData() { + return data; + } + + /** + * Get a list of materials names that was previously used by older versions. + * If the material was added in a new version {@link #isNewVersion()}, + * then the first element will indicate which version the material was added in. + * + * @return a list of legacy material names and the first element as the version the material was added in if new. + * @since 1.0.0 + */ + @Nonnull + public String[] getLegacy() { + return legacy; + } + + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #parseItem(boolean) + * @see #setType(ItemStack) + * @since 1.0.0 + */ + @Nullable + public ItemStack parseItem() { + return parseItem(false); + } + + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @param suggest if true {@link #parseMaterial(boolean)} true will be used. + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #setType(ItemStack) + * @since 2.0.0 + */ + @Nullable + @SuppressWarnings("deprecation") + public ItemStack parseItem(boolean suggest) { + Material material = this.parseMaterial(suggest); + if (material == null) return null; + return ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); + } + + /** + * Parses the material of this XMaterial. + * + * @return the material related to this XMaterial based on the server version. + * @see #parseMaterial(boolean) + * @since 1.0.0 + */ + @Nullable + public Material parseMaterial() { + return parseMaterial(false); + } + + /** + * Parses the material of this XMaterial and accepts suggestions. + * + * @param suggest use a suggested material (from older materials) if the material is added in a later version of Minecraft. + * @return the material related to this XMaterial based on the server version. + * @since 2.0.0 + */ + @SuppressWarnings("OptionalAssignedToNull") + @Nullable + public Material parseMaterial(boolean suggest) { + Optional cache = PARSED_CACHE.getIfPresent(this); + if (cache != null) return cache.orElse(null); + Material mat; + + if (!ISFLAT && this.isDuplicated()) mat = requestOldMaterial(suggest); + else { + mat = Material.getMaterial(this.name()); + if (mat == null) mat = requestOldMaterial(suggest); + } + + Optional opt = Optional.ofNullable(mat); + PARSED_CACHE.put(this, opt); + return mat; + } + + /** + * Parses a material for older versions of Minecraft. + * Accepts suggestions if specified. + * + * @param suggest if true suggested materials will be considered for old versions. + * @return a parsed material suitable for the current Minecraft version. + * @see #parseMaterial(boolean) + * @since 2.0.0 + */ + @Nullable + private Material requestOldMaterial(boolean suggest) { + for (int i = this.legacy.length - 1; i >= 0; i--) { + String legacy = this.legacy[i]; + + // Check if we've reached the end and the last string is our + // material version. + if (i == 0 && legacy.charAt(1) == '.') return null; + + // According to the suggestion list format, all the other names continuing + // from here are considered as a "suggestion" + // The empty string is an indicator for suggestion list on the left side. + if (legacy.isEmpty()) { + if (suggest) continue; + break; + } + + Material material = Material.getMaterial(legacy); + if (material != null) return material; + } + return null; + } + + /** + * Checks if an item has the same material (and data value on older versions). + * + * @param item item to check. + * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public boolean isSimilar(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot compare with null ItemStack"); + if (item.getType() != this.parseMaterial()) return false; + return ISFLAT || this.isDamageable() || item.getDurability() == this.data; + } + + /** + * Gets the suggested material names that can be used + * if the material is not supported in the current version. + * + * @return a list of suggested material names. + * @see #parseMaterial(boolean) + * @since 2.0.0 + */ + @Nonnull + public List getSuggestions() { + if (this.legacy.length == 0 || this.legacy[0].charAt(1) != '.') return new ArrayList<>(); + List suggestions = new ArrayList<>(); + for (String legacy : this.legacy) { + if (legacy.isEmpty()) break; + suggestions.add(legacy); + } + return suggestions; + } + + /** + * Checks if this material is supported in the current version. + * Suggested materials will be ignored. + *

+ * Note that you should use {@link #parseMaterial()} and check if it's null + * if you're going to parse and use the material later. + * + * @return true if the material exists in {@link Material} list. + * @since 2.0.0 + */ + public boolean isSupported() { + int version = this.getMaterialVersion(); + if (version != 0) return supports(version); + + Material material = Material.getMaterial(this.name()); + if (material != null) return true; + return requestOldMaterial(false) != null; + } + + /** + * Checks if the material is newly added after the 1.13 Aquatic Update. + * + * @return true if the material was newly added, otherwise false. + * @see #getMaterialVersion() + * @since 2.0.0 + */ + public boolean isFromNewSystem() { + return this.legacy.length != 0 && Integer.parseInt(this.legacy[0].substring(2)) > 13; + } +} \ No newline at end of file diff --git a/src/plugin.yml b/src/plugin.yml index fb02282..d0afbed 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -9,8 +9,6 @@ depend: ['WorldGuard'] commands: mctpaudio: description: Main command - mctpshow: - description: Show command audio: description: Connect command