From a8fc563be3ecb4c3842fcd8c59d7ca0c13864541 Mon Sep 17 00:00:00 2001 From: Amaury Carrade Date: Thu, 26 Nov 2020 16:26:36 +0100 Subject: [PATCH] Added methods to ItemUtils to convert from a material family and a color to a material (#64) * Added methods to colorize a material. As we don't have any way to get a colorized version from a color and a base material (like, get Material.BLUE_BED from BED and BLUE, both of them being dynamic), here are methods to do so. * Added tests for colors to blocks conversions - Added tests - Added checkstyle config to allow to suppress warning through annotation * Updated javadoc and changelog * Fixed license headers * ItemUtils.asDye now returns an Optional --- CHANGELOG.md | 12 ++ checkstyle.xml | 1 + .../tools/items/ColorableMaterial.java | 61 ++++++++++ .../quartzlib/tools/items/ItemUtils.java | 111 ++++++++++++++++-- .../quartzlib/tools/items/ItemUtilsTest.java | 109 +++++++++++++++++ 5 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/tools/items/ColorableMaterial.java create mode 100644 src/test/java/fr/zcraft/quartzlib/tools/items/ItemUtilsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a285b7c9..bda97376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,18 @@ _Published one day_ ``` To hide _all_ attributes from the item, use `hideAllAttributes()`. + +#### [`ItemUtils`](https://zdevelopers.github.io/QuartzLib/fr/zcraft/quartzlib/tools/items/ItemUtils.html) + +- We added a [`asDye`](https://zdevelopers.github.io/QuartzLib/fr/zcraft/quartzlib/tools/items/ItemUtils.html#asDye-org.bukkit.ChatColor-) method to convert a `ChatColor` to its closest `DyeColor` equivalent. + +- We added two `colorize` methods to `ItemUtils` to convert either [a dye](https://zdevelopers.github.io/QuartzLib/fr/zcraft/quartzlib/tools/items/ItemUtils.html#colorize-fr.zcraft.quartzlib.tools.items.ColorableMaterial-org.bukkit.DyeColor-) or [a chat color](https://zdevelopers.github.io/QuartzLib/fr/zcraft/quartzlib/tools/items/ItemUtils.html#colorize-fr.zcraft.quartzlib.tools.items.ColorableMaterial-org.bukkit.ChatColor-) to a colored block dynamically. As example, + + ```java + ItemUtils.colorize(ColorableMaterial.GLAZED_TERRACOTTA, DyeColor.LIME) + ``` + + will return `Material.LIME_GLAZED_TERRACOTTA`. #### Tests diff --git a/checkstyle.xml b/checkstyle.xml index 2daebde3..18124ea4 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -48,6 +48,7 @@ + diff --git a/src/main/java/fr/zcraft/quartzlib/tools/items/ColorableMaterial.java b/src/main/java/fr/zcraft/quartzlib/tools/items/ColorableMaterial.java new file mode 100644 index 00000000..8a30b41a --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/tools/items/ColorableMaterial.java @@ -0,0 +1,61 @@ +/* + * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) + * + * This software is governed by the CeCILL-B license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-B + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-B license and that you accept its terms. + */ + +package fr.zcraft.quartzlib.tools.items; + +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; + +/** + * A {@link Material} with color variants. + * + *

The name of the enum item is the name of the material, without the color part.

+ * + * @see ItemUtils#colorize(ColorableMaterial, DyeColor) Compute a {@link Material} from a {@link ColorableMaterial} + * and a {@link DyeColor}. + * @see ItemUtils#colorize(ColorableMaterial, ChatColor) Compute a {@link Material} from a {@link ColorableMaterial} + * and a {@link ChatColor}. + */ +public enum ColorableMaterial { + BANNER, + BED, + CARPET, + CONCRETE, + CONCRETE_POWDER, + DYE, + GLAZED_TERRACOTTA, + SHULKER_BOX, + STAINED_GLASS, + STAINED_GLASS_PANE, + TERRACOTTA, + WALL_BANNER, + WOOL +} diff --git a/src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java b/src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java index 313f08d1..95a9bb12 100644 --- a/src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java @@ -37,7 +37,10 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Random; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -47,6 +50,9 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.Potion; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; //import org.bukkit.Sound; @@ -103,7 +109,7 @@ public static ItemStack consumeItem(Player player, ItemStack item) { * @param player The player to give the item to. * @param item The item to give to the player * @return true if the player received the item in its inventory, false if - * it had to be totally or partially dropped on the ground. + * it had to be totally or partially dropped on the ground. */ public static boolean give(final Player player, final ItemStack item) { final Map leftover = player.getInventory().addItem(item); @@ -152,8 +158,7 @@ public static boolean areSimilar(ItemStack first, ItemStack other) { && first.getData().equals(other.getData()) && ((!first.hasItemMeta() && !other.hasItemMeta()) || (!first.getItemMeta().hasDisplayName() && !other.getItemMeta().hasDisplayName()) - || (first.getItemMeta().getDisplayName().equals(other.getItemMeta().getDisplayName())) - ); + || (first.getItemMeta().getDisplayName().equals(other.getItemMeta().getDisplayName()))); } /** @@ -326,7 +331,7 @@ private static Method getRegistryLookupMethod() throws NMSException { * * @param item An item. * @return The Minecraft name of this item, or null if the item's material - * is invalid. + * is invalid. * @throws NMSException if the operation cannot be executed. */ public static String getMinecraftId(ItemStack item) throws NMSException { @@ -407,8 +412,8 @@ public static Object asCraftCopy(ItemStack item) throws NMSException { * * @param item An item. * @return A NMS ItemStack for this item. If the item was a CraftItemStack, - * this will be the item's handle directly; in the other cases, a copy in a - * NMS ItemStack object. + * this will be the item's handle directly; in the other cases, a copy in a + * NMS ItemStack object. * @throws NMSException if the operation cannot be executed. */ public static Object getNMSItemStack(ItemStack item) throws NMSException { @@ -427,8 +432,8 @@ public static Object getNMSItemStack(ItemStack item) throws NMSException { * * @param item An item. * @return A CraftItemStack for this item. If the item was initially a - * CraftItemStack, it is returned directly. In the other cases, a copy in a - * new CraftItemStack will be returned. + * CraftItemStack, it is returned directly. In the other cases, + * a copy in a new CraftItemStack will be returned. * @throws NMSException if the operation cannot be executed. */ public static Object getCraftItemStack(ItemStack item) throws NMSException { @@ -517,4 +522,94 @@ public static void dropLater(final Location location, final ItemStack item) { RunTask.nextTick(() -> drop(location, item)); } + /** + * Converts a chat color to its dye equivalent. + * + *

The transformation is not perfect as there is no 1:1 + * correspondence between dyes and chat colors.

+ * + * @param color The chat color. + * @return The corresponding dye, or an empty value if none match (e.g. for formatting codes, of for {@code null}). + */ + @Contract(pure = true) + public static Optional asDye(@Nullable final ChatColor color) { + if (color == null) { + return Optional.empty(); + } + + switch (color) { + case BLACK: + return Optional.of(DyeColor.BLACK); + + case BLUE: + case DARK_BLUE: + return Optional.of(DyeColor.BLUE); + + case DARK_GREEN: + return Optional.of(DyeColor.GREEN); + + case DARK_AQUA: + return Optional.of(DyeColor.CYAN); + + case DARK_RED: + return Optional.of(DyeColor.RED); + + case DARK_PURPLE: + return Optional.of(DyeColor.PURPLE); + + case GOLD: + case YELLOW: + return Optional.of(DyeColor.YELLOW); + + case GRAY: + return Optional.of(DyeColor.LIGHT_GRAY); + + case DARK_GRAY: + return Optional.of(DyeColor.GRAY); + + case GREEN: + return Optional.of(DyeColor.LIME); + + case AQUA: + return Optional.of(DyeColor.LIGHT_BLUE); + + case RED: + return Optional.of(DyeColor.ORANGE); + + case LIGHT_PURPLE: + return Optional.of(DyeColor.PINK); + + case WHITE: + return Optional.of(DyeColor.WHITE); + + // White, reset & formatting + default: + return Optional.empty(); + } + } + + /** + * Converts a dye color to a dyeable material. + * + * @param material The colorable material to colorize. + * @param color The dye color. + * @return The corresponding material. + */ + @Contract(pure = true) + public static Material colorize(@NotNull final ColorableMaterial material, @NotNull final DyeColor color) { + return Material.valueOf(color.name() + "_" + material.name()); + } + + /** + * Converts a chat color to a dyeable material. + * + * @param material The colorable material to colorize. + * @param color The chat color. + * @return The corresponding material. If the chat color was not convertible to a dye, {@code ChatColor#WHITE} is + * used. + */ + @Contract(pure = true) + public static Material colorize(@NotNull final ColorableMaterial material, @NotNull final ChatColor color) { + return colorize(material, asDye(color).orElse(DyeColor.WHITE)); + } } diff --git a/src/test/java/fr/zcraft/quartzlib/tools/items/ItemUtilsTest.java b/src/test/java/fr/zcraft/quartzlib/tools/items/ItemUtilsTest.java new file mode 100644 index 00000000..d0681560 --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/tools/items/ItemUtilsTest.java @@ -0,0 +1,109 @@ +/* + * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) + * + * This software is governed by the CeCILL-B license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL-B + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-B license and that you accept its terms. + */ + +package fr.zcraft.quartzlib.tools.items; + +import com.google.common.collect.ImmutableMap; +import fr.zcraft.quartzlib.MockedBukkitTest; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("checkstyle:linelength") // Justification: tests are much more readable with one per line +public class ItemUtilsTest extends MockedBukkitTest { + + @Test + public void chatColorAreCorrectlyConvertedToDye() { + final Map expectedConversion = ImmutableMap.builder() + // All 16 colours are converted to their closest match + .put(ChatColor.BLACK, DyeColor.BLACK) + .put(ChatColor.BLUE, DyeColor.BLUE) + .put(ChatColor.DARK_BLUE, DyeColor.BLUE) + .put(ChatColor.GREEN, DyeColor.LIME) + .put(ChatColor.DARK_GREEN, DyeColor.GREEN) + .put(ChatColor.DARK_AQUA, DyeColor.CYAN) + .put(ChatColor.DARK_RED, DyeColor.RED) + .put(ChatColor.DARK_PURPLE, DyeColor.PURPLE) + .put(ChatColor.GOLD, DyeColor.YELLOW) + .put(ChatColor.YELLOW, DyeColor.YELLOW) + .put(ChatColor.GRAY, DyeColor.LIGHT_GRAY) + .put(ChatColor.DARK_GRAY, DyeColor.GRAY) + .put(ChatColor.AQUA, DyeColor.LIGHT_BLUE) + .put(ChatColor.RED, DyeColor.ORANGE) + .put(ChatColor.LIGHT_PURPLE, DyeColor.PINK) + .put(ChatColor.WHITE, DyeColor.WHITE) + + .build(); + + final Set dyes = new HashSet<>(expectedConversion.values()); + Assert.assertEquals( + "All dye colors are matched against something except brown and magenta", + dyes.size(), DyeColor.values().length - 2 + ); + + Arrays.stream(ChatColor.values()).forEach(chatColor -> { + final DyeColor dye = expectedConversion.get(chatColor); + Assert.assertEquals( + chatColor + " is correctly converted to" + (dye != null ? dye : "Optional.EMPTY"), + ItemUtils.asDye(chatColor), + dye != null ? Optional.of(dye) : Optional.empty() + ); + }); + } + + @Test + public void blocksCanBeColorizedWithDyeColors() { + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.BANNER, DyeColor.BLUE), Material.BLUE_BANNER); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.BED, DyeColor.LIME), Material.LIME_BED); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CARPET, DyeColor.GREEN), Material.GREEN_CARPET); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CONCRETE, DyeColor.BROWN), Material.BROWN_CONCRETE); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CONCRETE_POWDER, DyeColor.ORANGE), Material.ORANGE_CONCRETE_POWDER); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.DYE, DyeColor.BLACK), Material.BLACK_DYE); + } + + @Test + public void blocksCanBeColorizedWithChatColors() { + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.GLAZED_TERRACOTTA, ChatColor.AQUA), Material.LIGHT_BLUE_GLAZED_TERRACOTTA); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.SHULKER_BOX, ChatColor.DARK_AQUA), Material.CYAN_SHULKER_BOX); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.STAINED_GLASS, ChatColor.DARK_RED), Material.RED_STAINED_GLASS); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.STAINED_GLASS_PANE, ChatColor.RED), Material.ORANGE_STAINED_GLASS_PANE); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.TERRACOTTA, ChatColor.LIGHT_PURPLE), Material.PINK_TERRACOTTA); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.WALL_BANNER, ChatColor.MAGIC), Material.WHITE_WALL_BANNER); + Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.WOOL, ChatColor.STRIKETHROUGH), Material.WHITE_WOOL); + } +}