diff --git a/src/main/java/com/Acrobot/Breeze/Utils/MaterialUtil.java b/src/main/java/com/Acrobot/Breeze/Utils/MaterialUtil.java index b2c2a79..3b70f5d 100644 --- a/src/main/java/com/Acrobot/Breeze/Utils/MaterialUtil.java +++ b/src/main/java/com/Acrobot/Breeze/Utils/MaterialUtil.java @@ -31,6 +31,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static com.Acrobot.Breeze.Utils.StringUtil.getMinecraftCharWidth; +import static com.Acrobot.Breeze.Utils.StringUtil.getMinecraftStringWidth; + /** * @author Acrobot */ @@ -40,7 +43,13 @@ public class MaterialUtil { public static final boolean LONG_NAME = true; public static final boolean SHORT_NAME = false; + /** + * @deprecated Use {@link MaterialUtil#MAXIMUM_SIGN_WIDTH} + */ + @Deprecated public static final short MAXIMUM_SIGN_LETTERS = 15; + // 15 dashes fit on one sign line with the default resource pack: + public static final int MAXIMUM_SIGN_WIDTH = (short) getMinecraftStringWidth("---------------"); private static final SimpleCache MATERIAL_CACHE = new SimpleCache<>(Properties.CACHE_SIZE); @@ -155,17 +164,17 @@ public class MaterialUtil { * @return ItemStack's name */ public static String getSignName(ItemStack itemStack) { - return getName(itemStack, MAXIMUM_SIGN_LETTERS); + return getName(itemStack, MAXIMUM_SIGN_WIDTH); } /** - * Returns item's name, with a maximum length + * Returns item's name, with a maximum width * * @param itemStack ItemStack to name - * @param maxLength The max length that the name should have; 0 or below if it should be unlimited + * @param maxWidth The max width that the name should have; 0 or below if it should be unlimited * @return ItemStack's name */ - public static String getName(ItemStack itemStack, int maxLength) { + public static String getName(ItemStack itemStack, int maxWidth) { String alias = Odd.getAlias(itemStack); String itemName = alias != null ? alias : itemStack.getType().toString(); @@ -179,18 +188,18 @@ public class MaterialUtil { metaData = "#" + Metadata.getItemCode(itemStack); } - int codeLength = (itemName + durability + metaData).length(); + int codeWidth = getMinecraftStringWidth(itemName + durability + metaData); String code = itemName; - if (maxLength > 0 && codeLength > maxLength) { - int exceeding = codeLength - maxLength; - code = getShortenedName(code, code.length() - exceeding); + if (maxWidth > 0 && codeWidth > maxWidth) { + int exceeding = codeWidth - maxWidth; + code = getShortenedName(code, getMinecraftStringWidth(code) - exceeding); } code = StringUtil.capitalizeFirstLetter(code, '_') + durability + metaData; ItemStack codeItem = getItem(code); if (!equals(itemStack, codeItem)) { - throw new IllegalArgumentException("Cannot generate code for item " + itemStack + " with maximum length of " + maxLength + throw new IllegalArgumentException("Cannot generate code for item " + itemStack + " with maximum length of " + maxWidth + " (code " + code + " results in item " + codeItem + ")"); } @@ -201,44 +210,54 @@ public class MaterialUtil { * Get an item name shortened to a max length that is still reversable by {@link #getMaterial(String)} * * @param itemName The name of the item - * @param maxLength The max length + * @param maxWidth The max width * @return The name shortened to the max length */ - public static String getShortenedName(String itemName, int maxLength) { - if (itemName.length() <= maxLength) { + public static String getShortenedName(String itemName, int maxWidth) { + int width = getMinecraftStringWidth(itemName); + if (width <= maxWidth) { return itemName; } - int exceeding = itemName.length() - maxLength; + int exceeding = width - maxWidth; String[] itemParts = itemName.split("_"); int shortestIndex = 0; int longestIndex = 0; for (int i = 0; i < itemParts.length; i++) { - if (itemParts[longestIndex].length() < itemParts[i].length()) { + if (getMinecraftStringWidth(itemParts[longestIndex]) < getMinecraftStringWidth(itemParts[i])) { longestIndex = i; } - if (itemParts[shortestIndex].length() > itemParts[i].length()) { + if (getMinecraftStringWidth(itemParts[shortestIndex]) > getMinecraftStringWidth(itemParts[i])) { shortestIndex = i; } } - for (int i = itemParts.length - 1; i >= 0 && exceeding > 0; i--) { int remove = 0; - if (itemParts[i].length() > itemParts[shortestIndex].length()) { - remove = itemParts[i].length() - itemParts[shortestIndex].length(); + int partWidth = getMinecraftStringWidth(itemParts[i]); + int shortestWidth = getMinecraftStringWidth(itemParts[shortestIndex]); + + if (partWidth > shortestWidth) { + remove = partWidth - shortestWidth; } + if (remove > exceeding) { remove = exceeding; } - itemParts[i] = itemParts[i].substring(0, itemParts[i].length() - remove); - exceeding -= remove; - } - while (exceeding > 0) { - for (int i = itemParts.length - 1; i >= 0 && exceeding > 0; i--) { + + while (remove > 0) { + int endWidth = getMinecraftCharWidth(itemParts[i].charAt(itemParts[i].length() - 1)); itemParts[i] = itemParts[i].substring(0, itemParts[i].length() - 1); - exceeding--; + remove -= endWidth; + exceeding -= endWidth; } } + while (exceeding > 0) { + for (int i = itemParts.length - 1; i >= 0 && exceeding > 0; i--) { + int endWidth = getMinecraftCharWidth(itemParts[i].charAt(itemParts[i].length() - 1)); + itemParts[i] = itemParts[i].substring(0, itemParts[i].length() - 1); + exceeding -= endWidth; + } + } return String.join("_", itemParts); } diff --git a/src/main/java/com/Acrobot/Breeze/Utils/StringUtil.java b/src/main/java/com/Acrobot/Breeze/Utils/StringUtil.java index 84d8395..eeeaef8 100644 --- a/src/main/java/com/Acrobot/Breeze/Utils/StringUtil.java +++ b/src/main/java/com/Acrobot/Breeze/Utils/StringUtil.java @@ -77,4 +77,39 @@ public class StringUtil { return output; } + + private static String characters = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'abcdefghijklmnopqrstuvwxyz{|}~¦ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»"; + private static int[] extraWidth = {4,2,5,6,6,6,6,3,5,5,5,6,2,6,2,6,6,6,6,6,6,6,6,6,6,6,2,2,5,6,5,6,7,6,6,6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,4,6,6,3,6,6,6,6,6,5,6,6,2,6,5,3,6,6,6,6,6,6,6,4,6,6,6,6,6,6,5,2,5,7,6,6,6,6,6,6,6,6,6,6,6,6,4,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,6,3,6,6,6,6,6,6,6,7,6,6,6,2,6,6,8,9,9,6,6,6,8,8,6,8,8,8,8,8,6,6,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,6,9,9,9,5,9,9,8,7,7,8,7,8,8,8,7,8,8,7,9,9,6,7,7,7,7,7,9,6,7,8,7,6,6,9,7,6,7,1}; + + /** + * Get the width that a character is displayed with in the default resource pack. + * This relies on a hardcoded character to width mapping and might not be precise in places. + * @param c The character to get the width of + * @return The width of the character (will return 10 for characters that we don't know the width of) + */ + public static int getMinecraftCharWidth(char c) { + if (c != ChatColor.COLOR_CHAR) { + int index = characters.indexOf(c); + if (index > -1) { + return extraWidth[index]; + } else { + return 10; + } + } + return 0; + } + + /** + * Get the width that a string is displayed with in the default resource pack. + * This relies on a hardcoded character to width mapping and might not be precise in places. + * @param string The string to get the width of + * @return The width of the string + */ + public static int getMinecraftStringWidth(String string) { + int width = 0; + for (char c : string.toCharArray()) { + width += getMinecraftCharWidth(c); + } + return width; + } } diff --git a/src/main/java/com/Acrobot/ChestShop/Configuration/Messages.java b/src/main/java/com/Acrobot/ChestShop/Configuration/Messages.java index 3160f01..2b5b11b 100644 --- a/src/main/java/com/Acrobot/ChestShop/Configuration/Messages.java +++ b/src/main/java/com/Acrobot/ChestShop/Configuration/Messages.java @@ -67,7 +67,7 @@ public class Messages { @PrecededBySpace public static String PLAYER_NOT_FOUND = "Player not found!"; public static String NO_PERMISSION = "You don't have permissions to do that!"; - public static String INCORRECT_ITEM_ID = "You have specified invalid item id!"; + public static String INCORRECT_ITEM_ID = "You have specified an invalid item id!"; public static String NOT_ENOUGH_PROTECTIONS = "Could not create a protection!"; @PrecededBySpace diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/ItemChecker.java b/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/ItemChecker.java index aa6d7f1..3db1eb7 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/ItemChecker.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/ItemChecker.java @@ -1,6 +1,7 @@ package com.Acrobot.ChestShop.Listeners.PreShopCreation; import com.Acrobot.Breeze.Utils.MaterialUtil; +import com.Acrobot.Breeze.Utils.StringUtil; import com.Acrobot.ChestShop.Configuration.Messages; import com.Acrobot.ChestShop.Configuration.Properties; import com.Acrobot.ChestShop.Events.PreShopCreationEvent; @@ -33,22 +34,17 @@ public class ItemChecker implements Listener { if (item == null) { if (Properties.ALLOW_AUTO_ITEM_FILL && itemCode.equals(AUTOFILL_CODE)) { - boolean foundItem = false; Chest chest = uBlock.findConnectedChest(event.getSign()); if (chest != null) { for (ItemStack stack : chest.getInventory().getContents()) { if (!MaterialUtil.isEmpty(stack)) { - itemCode = MaterialUtil.getSignName(stack); - - event.setSignLine(ITEM_LINE, itemCode); - foundItem = true; - + item = stack; break; } } } - if (!foundItem) { + if (item == null) { event.setSignLine(ITEM_LINE, ChatColor.BOLD + ChestShopSign.AUTOFILL_CODE); event.setOutcome(ITEM_AUTOFILL); event.getPlayer().sendMessage(Messages.prefix(Messages.CLICK_TO_AUTOFILL_ITEM)); @@ -60,10 +56,14 @@ public class ItemChecker implements Listener { } } - if (itemCode.length() > MAXIMUM_SIGN_LETTERS) { + itemCode = MaterialUtil.getSignName(item); + + if (StringUtil.getMinecraftStringWidth(itemCode) > MAXIMUM_SIGN_WIDTH) { event.setOutcome(INVALID_ITEM); return; } + + event.setSignLine(ITEM_LINE, itemCode); } private static boolean isSameItem(String newCode, ItemStack item) { diff --git a/src/test/java/com/Acrobot/Breeze/Tests/MaterialTest.java b/src/test/java/com/Acrobot/Breeze/Tests/MaterialTest.java index 1554295..709319f 100644 --- a/src/test/java/com/Acrobot/Breeze/Tests/MaterialTest.java +++ b/src/test/java/com/Acrobot/Breeze/Tests/MaterialTest.java @@ -29,10 +29,11 @@ public class MaterialTest { @Test public void testCodes() { for (Material material : Material.values()) { - if (!material.isLegacy()) { - String shortenedName = MaterialUtil.getShortenedName(material.toString(), MaterialUtil.MAXIMUM_SIGN_LETTERS); - assertSame(shortenedName + " does not produce " + material, material, MaterialUtil.getMaterial(shortenedName)); + if (material.isLegacy()) { + continue; } + String shortenedName = MaterialUtil.getShortenedName(material.toString(), MaterialUtil.MAXIMUM_SIGN_WIDTH); + assertSame(material, MaterialUtil.getMaterial(shortenedName)); } } }