Skip to content

Commit a8fc563

Browse files
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<ChatColor>
1 parent 9f862f7 commit a8fc563

File tree

5 files changed

+286
-8
lines changed

5 files changed

+286
-8
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ _Published one day_
3636
```
3737

3838
To hide _all_ attributes from the item, use `hideAllAttributes()`.
39+
40+
#### [`ItemUtils`](https://zdevelopers.github.io/QuartzLib/fr/zcraft/quartzlib/tools/items/ItemUtils.html)
41+
42+
- 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.
43+
44+
- 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,
45+
46+
```java
47+
ItemUtils.colorize(ColorableMaterial.GLAZED_TERRACOTTA, DyeColor.LIME)
48+
```
49+
50+
will return `Material.LIME_GLAZED_TERRACOTTA`.
3951

4052
#### Tests
4153

checkstyle.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
</module>
4949

5050
<module name="TreeWalker">
51+
<module name="SuppressWarningsHolder"/>
5152
<module name="SuppressWarnings">
5253
<property name="id" value="checkstyle:suppresswarnings"/>
5354
</module>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright or © or Copr. QuartzLib contributors (2015 - 2020)
3+
*
4+
* This software is governed by the CeCILL-B license under French law and
5+
* abiding by the rules of distribution of free software. You can use,
6+
* modify and/ or redistribute the software under the terms of the CeCILL-B
7+
* license as circulated by CEA, CNRS and INRIA at the following URL
8+
* "http://www.cecill.info".
9+
*
10+
* As a counterpart to the access to the source code and rights to copy,
11+
* modify and redistribute granted by the license, users are provided only
12+
* with a limited warranty and the software's author, the holder of the
13+
* economic rights, and the successive licensors have only limited
14+
* liability.
15+
*
16+
* In this respect, the user's attention is drawn to the risks associated
17+
* with loading, using, modifying and/or developing or reproducing the
18+
* software by the user in light of its specific status of free software,
19+
* that may mean that it is complicated to manipulate, and that also
20+
* therefore means that it is reserved for developers and experienced
21+
* professionals having in-depth computer knowledge. Users are therefore
22+
* encouraged to load and test the software's suitability as regards their
23+
* requirements in conditions enabling the security of their systems and/or
24+
* data to be ensured and, more generally, to use and operate it in the
25+
* same conditions as regards security.
26+
*
27+
* The fact that you are presently reading this means that you have had
28+
* knowledge of the CeCILL-B license and that you accept its terms.
29+
*/
30+
31+
package fr.zcraft.quartzlib.tools.items;
32+
33+
import org.bukkit.ChatColor;
34+
import org.bukkit.DyeColor;
35+
import org.bukkit.Material;
36+
37+
/**
38+
* A {@link Material} with color variants.
39+
*
40+
* <p>The name of the enum item is the name of the material, without the color part.</p>
41+
*
42+
* @see ItemUtils#colorize(ColorableMaterial, DyeColor) Compute a {@link Material} from a {@link ColorableMaterial}
43+
* and a {@link DyeColor}.
44+
* @see ItemUtils#colorize(ColorableMaterial, ChatColor) Compute a {@link Material} from a {@link ColorableMaterial}
45+
* and a {@link ChatColor}.
46+
*/
47+
public enum ColorableMaterial {
48+
BANNER,
49+
BED,
50+
CARPET,
51+
CONCRETE,
52+
CONCRETE_POWDER,
53+
DYE,
54+
GLAZED_TERRACOTTA,
55+
SHULKER_BOX,
56+
STAINED_GLASS,
57+
STAINED_GLASS_PANE,
58+
TERRACOTTA,
59+
WALL_BANNER,
60+
WOOL
61+
}

src/main/java/fr/zcraft/quartzlib/tools/items/ItemUtils.java

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
import java.lang.reflect.Type;
3838
import java.util.List;
3939
import java.util.Map;
40+
import java.util.Optional;
4041
import java.util.Random;
42+
import org.bukkit.ChatColor;
43+
import org.bukkit.DyeColor;
4144
import org.bukkit.GameMode;
4245
import org.bukkit.Location;
4346
import org.bukkit.Material;
@@ -47,6 +50,9 @@
4750
import org.bukkit.inventory.meta.ItemMeta;
4851
import org.bukkit.inventory.meta.PotionMeta;
4952
import org.bukkit.potion.Potion;
53+
import org.jetbrains.annotations.Contract;
54+
import org.jetbrains.annotations.NotNull;
55+
import org.jetbrains.annotations.Nullable;
5056

5157
//import org.bukkit.Sound;
5258

@@ -103,7 +109,7 @@ public static ItemStack consumeItem(Player player, ItemStack item) {
103109
* @param player The player to give the item to.
104110
* @param item The item to give to the player
105111
* @return true if the player received the item in its inventory, false if
106-
* it had to be totally or partially dropped on the ground.
112+
* it had to be totally or partially dropped on the ground.
107113
*/
108114
public static boolean give(final Player player, final ItemStack item) {
109115
final Map<Integer, ItemStack> leftover = player.getInventory().addItem(item);
@@ -152,8 +158,7 @@ public static boolean areSimilar(ItemStack first, ItemStack other) {
152158
&& first.getData().equals(other.getData())
153159
&& ((!first.hasItemMeta() && !other.hasItemMeta())
154160
|| (!first.getItemMeta().hasDisplayName() && !other.getItemMeta().hasDisplayName())
155-
|| (first.getItemMeta().getDisplayName().equals(other.getItemMeta().getDisplayName()))
156-
);
161+
|| (first.getItemMeta().getDisplayName().equals(other.getItemMeta().getDisplayName())));
157162
}
158163

159164
/**
@@ -326,7 +331,7 @@ private static Method getRegistryLookupMethod() throws NMSException {
326331
*
327332
* @param item An item.
328333
* @return The Minecraft name of this item, or null if the item's material
329-
* is invalid.
334+
* is invalid.
330335
* @throws NMSException if the operation cannot be executed.
331336
*/
332337
public static String getMinecraftId(ItemStack item) throws NMSException {
@@ -407,8 +412,8 @@ public static Object asCraftCopy(ItemStack item) throws NMSException {
407412
*
408413
* @param item An item.
409414
* @return A NMS ItemStack for this item. If the item was a CraftItemStack,
410-
* this will be the item's handle directly; in the other cases, a copy in a
411-
* NMS ItemStack object.
415+
* this will be the item's handle directly; in the other cases, a copy in a
416+
* NMS ItemStack object.
412417
* @throws NMSException if the operation cannot be executed.
413418
*/
414419
public static Object getNMSItemStack(ItemStack item) throws NMSException {
@@ -427,8 +432,8 @@ public static Object getNMSItemStack(ItemStack item) throws NMSException {
427432
*
428433
* @param item An item.
429434
* @return A CraftItemStack for this item. If the item was initially a
430-
* CraftItemStack, it is returned directly. In the other cases, a copy in a
431-
* new CraftItemStack will be returned.
435+
* CraftItemStack, it is returned directly. In the other cases,
436+
* a copy in a new CraftItemStack will be returned.
432437
* @throws NMSException if the operation cannot be executed.
433438
*/
434439
public static Object getCraftItemStack(ItemStack item) throws NMSException {
@@ -517,4 +522,94 @@ public static void dropLater(final Location location, final ItemStack item) {
517522
RunTask.nextTick(() -> drop(location, item));
518523
}
519524

525+
/**
526+
* Converts a chat color to its dye equivalent.
527+
*
528+
* <p>The transformation is not perfect as there is no 1:1
529+
* correspondence between dyes and chat colors.</p>
530+
*
531+
* @param color The chat color.
532+
* @return The corresponding dye, or an empty value if none match (e.g. for formatting codes, of for {@code null}).
533+
*/
534+
@Contract(pure = true)
535+
public static Optional<DyeColor> asDye(@Nullable final ChatColor color) {
536+
if (color == null) {
537+
return Optional.empty();
538+
}
539+
540+
switch (color) {
541+
case BLACK:
542+
return Optional.of(DyeColor.BLACK);
543+
544+
case BLUE:
545+
case DARK_BLUE:
546+
return Optional.of(DyeColor.BLUE);
547+
548+
case DARK_GREEN:
549+
return Optional.of(DyeColor.GREEN);
550+
551+
case DARK_AQUA:
552+
return Optional.of(DyeColor.CYAN);
553+
554+
case DARK_RED:
555+
return Optional.of(DyeColor.RED);
556+
557+
case DARK_PURPLE:
558+
return Optional.of(DyeColor.PURPLE);
559+
560+
case GOLD:
561+
case YELLOW:
562+
return Optional.of(DyeColor.YELLOW);
563+
564+
case GRAY:
565+
return Optional.of(DyeColor.LIGHT_GRAY);
566+
567+
case DARK_GRAY:
568+
return Optional.of(DyeColor.GRAY);
569+
570+
case GREEN:
571+
return Optional.of(DyeColor.LIME);
572+
573+
case AQUA:
574+
return Optional.of(DyeColor.LIGHT_BLUE);
575+
576+
case RED:
577+
return Optional.of(DyeColor.ORANGE);
578+
579+
case LIGHT_PURPLE:
580+
return Optional.of(DyeColor.PINK);
581+
582+
case WHITE:
583+
return Optional.of(DyeColor.WHITE);
584+
585+
// White, reset & formatting
586+
default:
587+
return Optional.empty();
588+
}
589+
}
590+
591+
/**
592+
* Converts a dye color to a dyeable material.
593+
*
594+
* @param material The colorable material to colorize.
595+
* @param color The dye color.
596+
* @return The corresponding material.
597+
*/
598+
@Contract(pure = true)
599+
public static Material colorize(@NotNull final ColorableMaterial material, @NotNull final DyeColor color) {
600+
return Material.valueOf(color.name() + "_" + material.name());
601+
}
602+
603+
/**
604+
* Converts a chat color to a dyeable material.
605+
*
606+
* @param material The colorable material to colorize.
607+
* @param color The chat color.
608+
* @return The corresponding material. If the chat color was not convertible to a dye, {@code ChatColor#WHITE} is
609+
* used.
610+
*/
611+
@Contract(pure = true)
612+
public static Material colorize(@NotNull final ColorableMaterial material, @NotNull final ChatColor color) {
613+
return colorize(material, asDye(color).orElse(DyeColor.WHITE));
614+
}
520615
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright or © or Copr. QuartzLib contributors (2015 - 2020)
3+
*
4+
* This software is governed by the CeCILL-B license under French law and
5+
* abiding by the rules of distribution of free software. You can use,
6+
* modify and/ or redistribute the software under the terms of the CeCILL-B
7+
* license as circulated by CEA, CNRS and INRIA at the following URL
8+
* "http://www.cecill.info".
9+
*
10+
* As a counterpart to the access to the source code and rights to copy,
11+
* modify and redistribute granted by the license, users are provided only
12+
* with a limited warranty and the software's author, the holder of the
13+
* economic rights, and the successive licensors have only limited
14+
* liability.
15+
*
16+
* In this respect, the user's attention is drawn to the risks associated
17+
* with loading, using, modifying and/or developing or reproducing the
18+
* software by the user in light of its specific status of free software,
19+
* that may mean that it is complicated to manipulate, and that also
20+
* therefore means that it is reserved for developers and experienced
21+
* professionals having in-depth computer knowledge. Users are therefore
22+
* encouraged to load and test the software's suitability as regards their
23+
* requirements in conditions enabling the security of their systems and/or
24+
* data to be ensured and, more generally, to use and operate it in the
25+
* same conditions as regards security.
26+
*
27+
* The fact that you are presently reading this means that you have had
28+
* knowledge of the CeCILL-B license and that you accept its terms.
29+
*/
30+
31+
package fr.zcraft.quartzlib.tools.items;
32+
33+
import com.google.common.collect.ImmutableMap;
34+
import fr.zcraft.quartzlib.MockedBukkitTest;
35+
import java.util.Arrays;
36+
import java.util.HashSet;
37+
import java.util.Map;
38+
import java.util.Optional;
39+
import java.util.Set;
40+
import org.bukkit.ChatColor;
41+
import org.bukkit.DyeColor;
42+
import org.bukkit.Material;
43+
import org.junit.Assert;
44+
import org.junit.jupiter.api.Assertions;
45+
import org.junit.jupiter.api.Test;
46+
47+
@SuppressWarnings("checkstyle:linelength") // Justification: tests are much more readable with one per line
48+
public class ItemUtilsTest extends MockedBukkitTest {
49+
50+
@Test
51+
public void chatColorAreCorrectlyConvertedToDye() {
52+
final Map<ChatColor, DyeColor> expectedConversion = ImmutableMap.<ChatColor, DyeColor>builder()
53+
// All 16 colours are converted to their closest match
54+
.put(ChatColor.BLACK, DyeColor.BLACK)
55+
.put(ChatColor.BLUE, DyeColor.BLUE)
56+
.put(ChatColor.DARK_BLUE, DyeColor.BLUE)
57+
.put(ChatColor.GREEN, DyeColor.LIME)
58+
.put(ChatColor.DARK_GREEN, DyeColor.GREEN)
59+
.put(ChatColor.DARK_AQUA, DyeColor.CYAN)
60+
.put(ChatColor.DARK_RED, DyeColor.RED)
61+
.put(ChatColor.DARK_PURPLE, DyeColor.PURPLE)
62+
.put(ChatColor.GOLD, DyeColor.YELLOW)
63+
.put(ChatColor.YELLOW, DyeColor.YELLOW)
64+
.put(ChatColor.GRAY, DyeColor.LIGHT_GRAY)
65+
.put(ChatColor.DARK_GRAY, DyeColor.GRAY)
66+
.put(ChatColor.AQUA, DyeColor.LIGHT_BLUE)
67+
.put(ChatColor.RED, DyeColor.ORANGE)
68+
.put(ChatColor.LIGHT_PURPLE, DyeColor.PINK)
69+
.put(ChatColor.WHITE, DyeColor.WHITE)
70+
71+
.build();
72+
73+
final Set<DyeColor> dyes = new HashSet<>(expectedConversion.values());
74+
Assert.assertEquals(
75+
"All dye colors are matched against something except brown and magenta",
76+
dyes.size(), DyeColor.values().length - 2
77+
);
78+
79+
Arrays.stream(ChatColor.values()).forEach(chatColor -> {
80+
final DyeColor dye = expectedConversion.get(chatColor);
81+
Assert.assertEquals(
82+
chatColor + " is correctly converted to" + (dye != null ? dye : "Optional.EMPTY"),
83+
ItemUtils.asDye(chatColor),
84+
dye != null ? Optional.of(dye) : Optional.empty()
85+
);
86+
});
87+
}
88+
89+
@Test
90+
public void blocksCanBeColorizedWithDyeColors() {
91+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.BANNER, DyeColor.BLUE), Material.BLUE_BANNER);
92+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.BED, DyeColor.LIME), Material.LIME_BED);
93+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CARPET, DyeColor.GREEN), Material.GREEN_CARPET);
94+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CONCRETE, DyeColor.BROWN), Material.BROWN_CONCRETE);
95+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.CONCRETE_POWDER, DyeColor.ORANGE), Material.ORANGE_CONCRETE_POWDER);
96+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.DYE, DyeColor.BLACK), Material.BLACK_DYE);
97+
}
98+
99+
@Test
100+
public void blocksCanBeColorizedWithChatColors() {
101+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.GLAZED_TERRACOTTA, ChatColor.AQUA), Material.LIGHT_BLUE_GLAZED_TERRACOTTA);
102+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.SHULKER_BOX, ChatColor.DARK_AQUA), Material.CYAN_SHULKER_BOX);
103+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.STAINED_GLASS, ChatColor.DARK_RED), Material.RED_STAINED_GLASS);
104+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.STAINED_GLASS_PANE, ChatColor.RED), Material.ORANGE_STAINED_GLASS_PANE);
105+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.TERRACOTTA, ChatColor.LIGHT_PURPLE), Material.PINK_TERRACOTTA);
106+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.WALL_BANNER, ChatColor.MAGIC), Material.WHITE_WALL_BANNER);
107+
Assertions.assertEquals(ItemUtils.colorize(ColorableMaterial.WOOL, ChatColor.STRIKETHROUGH), Material.WHITE_WOOL);
108+
}
109+
}

0 commit comments

Comments
 (0)