Skip to content

Commit

Permalink
Added methods to ItemUtils to convert from a material family and a co…
Browse files Browse the repository at this point in the history
…lor 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<ChatColor>
  • Loading branch information
AmauryCarrade authored Nov 26, 2020
1 parent 9f862f7 commit a8fc563
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 8 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
</module>

<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="SuppressWarnings">
<property name="id" value="checkstyle:suppresswarnings"/>
</module>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>The name of the enum item is the name of the material, without the color part.</p>
*
* @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
}
111 changes: 103 additions & 8 deletions src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<Integer, ItemStack> leftover = player.getInventory().addItem(item);
Expand Down Expand Up @@ -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())));
}

/**
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
*
* <p>The transformation is not perfect as there is no 1:1
* correspondence between dyes and chat colors.</p>
*
* @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<DyeColor> 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));
}
}
109 changes: 109 additions & 0 deletions src/test/java/fr/zcraft/quartzlib/tools/items/ItemUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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<ChatColor, DyeColor> expectedConversion = ImmutableMap.<ChatColor, DyeColor>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<DyeColor> 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);
}
}

0 comments on commit a8fc563

Please sign in to comment.