package think.rpgitems.data; import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.map.hash.TIntObjectHashMap; import java.io.UnsupportedEncodingException; import java.util.List; import org.bukkit.ChatColor; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; /** * RPGMetadata is a (bad) way of tagging extra data onto an item without it showing up on the item * itself. This uses a similar system to Notch's entity metadata system. The data will be encoded in * minecraft colour codes which makes this really inefficient because 1 byte -> 2 bytes(Hex) -> 4 * bytes( Colour codes). RPGMetadatais prefixed with CAFE to allow it to be easily detected. * */ public class RPGMetadata extends TIntObjectHashMap<Object> { private final static String METADATA_PREFIX = ChatColor.COLOR_CHAR + "c" + ChatColor.COLOR_CHAR + "a" + ChatColor.COLOR_CHAR + "f" + ChatColor.COLOR_CHAR + "e"; public static final int DURABILITY = 0; public static boolean hasRPGMetadata(ItemStack item) { if (!item.hasItemMeta()) return false; ItemMeta meta = item.getItemMeta(); if (!meta.hasLore()) return false; List<String> lore = meta.getLore(); if (lore.size() == 0) return false; return lore.get(0).contains(METADATA_PREFIX); } public static RPGMetadata parseLoreline(String lore) { RPGMetadata meta = new RPGMetadata(); int pos = lore.indexOf(METADATA_PREFIX); if (pos == -1) { return meta; } String lenStr = lore.substring(pos + METADATA_PREFIX.length(), pos + METADATA_PREFIX.length() + 8); int length = parseShort(lenStr, 0); String data = lore.substring(pos + METADATA_PREFIX.length() + 8, pos + METADATA_PREFIX.length() + 8 + length); int off = 0; while (off < length) { int index = parseByte(data, off); off += 4; int key = index & 0x1F; int type = index >> 5; switch(type) { case 0: //Byte int byteValue = parseByte(data, off); off += 4; meta.put(key, Byte.valueOf((byte) byteValue)); break; case 1: //Short int shortValue = parseShort(data, off); off += 8; meta.put(key, Short.valueOf((short) shortValue)); break; case 2: //Int int intValue = parseInt(data, off); off += 16; meta.put(key, Integer.valueOf(intValue)); break; case 3: //Float int floatValueBits = parseInt(data, off); off += 16; meta.put(key, Float.intBitsToFloat(floatValueBits)); break; case 4: //String int stringLength = parseShort(data, off); off += 8; byte[] bytes = new byte[stringLength]; for (int i = 0; i < stringLength; i++) { bytes[i] = (byte) parseByte(data, off); off += 4; } try { meta.put(key, new String(bytes, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } break; } } return meta; } private static int parseInt(String iStr, int off) { return (parseShort(iStr, off + 0) << 16) | parseShort(iStr, off + 8); } private static int parseShort(String sStr, int off) { return (parseByte(sStr, off + 0) << 8) | parseByte(sStr, off + 4); } private static int parseByte(String bStr, int off) { return Integer.parseInt("" + bStr.charAt(off + 1) + bStr.charAt(off + 3), 16); } public RPGMetadata() { } public String toMCString() { StringBuilder out = new StringBuilder(); out.append(METADATA_PREFIX); TIntObjectIterator<Object> it = iterator(); while (it.hasNext()) { it.advance(); int key = it.key(); Object value = it.value(); int index = key & 0x1F; if (value instanceof Byte) { index |= 0 << 5; writeByte(out, index); writeByte(out, ((Byte) value).intValue()); } else if (value instanceof Short) { index |= 1 << 5; writeByte(out, index); writeShort(out, ((Short) value).intValue()); } else if (value instanceof Integer) { index |= 2 << 5; writeByte(out, index); writeInt(out, ((Integer) value).intValue()); } else if (value instanceof Float) { index |= 3 << 5; writeByte(out, index); writeInt(out, Float.floatToIntBits((Float) value)); } else if (value instanceof String) { index |= 4 << 5; writeByte(out, index); writeString(out, (String) value); } } int size = out.length() - METADATA_PREFIX.length(); insertShort(out, METADATA_PREFIX.length(), size); return out.toString(); } private void writeString(StringBuilder out, String value) { try { byte[] data = value.getBytes("UTF-8"); writeShort(out, data.length); for (byte b : data) { writeByte(out, b); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } private void writeByte(StringBuilder out, int b) { String hex = Integer.toString(b, 16); out.append(ChatColor.COLOR_CHAR); out.append(hex.length() == 1 ? "0" : hex.charAt(0)); out.append(ChatColor.COLOR_CHAR); out.append(hex.charAt(hex.length() - 1)); } private void writeShort(StringBuilder out, int s) { writeByte(out, s >> 8); writeByte(out, s & 0xFF); } private void writeInt(StringBuilder out, int s) { writeShort(out, s >> 16); writeShort(out, s & 0xFFFF); } private void insertByte(StringBuilder out, int offset, int b) { String hex = Integer.toString(b, 16); out.insert(offset, ChatColor.COLOR_CHAR); out.insert(offset + 1, hex.length() == 1 ? "0" : hex.charAt(0)); out.insert(offset + 2, ChatColor.COLOR_CHAR); out.insert(offset + 3, hex.charAt(hex.length() - 1)); } private void insertShort(StringBuilder out, int offset, int s) { insertByte(out, offset, s >> 8); insertByte(out, offset + 4, s & 0xFF); } private void insertInt(StringBuilder out, int offset, int s) { insertShort(out, offset, s >> 16); insertShort(out, offset + 8, s & 0xFFFF); } }