diff options
author | Wesley Wolfe <weswolf@aol.com> | 2012-12-17 01:16:28 -0600 |
---|---|---|
committer | Wesley Wolfe <weswolf@aol.com> | 2012-12-17 01:16:28 -0600 |
commit | c02306b55e67d33e0b1e582977727b61db9b35c5 (patch) | |
tree | 8822b074e0828bd6a7d4e3b2514754e52b29f109 | |
parent | 84912b55103d3904d8b47974f553924a18cfd681 (diff) | |
download | bukkit-c02306b55e67d33e0b1e582977727b61db9b35c5.tar bukkit-c02306b55e67d33e0b1e582977727b61db9b35c5.tar.gz bukkit-c02306b55e67d33e0b1e582977727b61db9b35c5.tar.lz bukkit-c02306b55e67d33e0b1e582977727b61db9b35c5.tar.xz bukkit-c02306b55e67d33e0b1e582977727b61db9b35c5.zip |
Add ItemMeta factory and interfaces. This adds BUKKIT-15
Included with ItemMeta is a new serializable class Color.
PotionEffects are now serializable.
25 files changed, 1656 insertions, 189 deletions
@@ -138,7 +138,7 @@ <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> - <version>1.2.1</version> + <version>1.3</version> <scope>test</scope> </dependency> </dependencies> diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index e2506bc1..ba5f6d74 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -26,6 +26,7 @@ import org.bukkit.plugin.messaging.Messenger; import org.bukkit.scheduler.BukkitScheduler; import com.avaje.ebean.config.ServerConfig; +import org.bukkit.inventory.ItemFactory; /** * Represents the Bukkit core, for version and Server singleton handling @@ -390,4 +391,8 @@ public final class Bukkit { public static WarningState getWarningState() { return server.getWarningState(); } + + public static ItemFactory getItemFactory() { + return server.getItemFactory(); + } } diff --git a/src/main/java/org/bukkit/Color.java b/src/main/java/org/bukkit/Color.java new file mode 100644 index 00000000..e5ddc422 --- /dev/null +++ b/src/main/java/org/bukkit/Color.java @@ -0,0 +1,339 @@ +package org.bukkit; + +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import com.google.common.collect.ImmutableMap; + +/** + * A container for a color palette. This class is immutable; the set methods return a new color. + * The color names listed as fields are HTML4 standards, but subject to change. + */ +@SerializableAs("Color") +public final class Color implements ConfigurationSerializable { + private static final int BIT_MASK = 0xff; + + /** + * White, or (0xFF,0xFF,0xFF) in (R,G,B) + */ + public static final Color WHITE = fromRGB(0xFFFFFF); + + /** + * Silver, or (0xC0,0xC0,0xC0) in (R,G,B) + */ + public static final Color SILVER = fromRGB(0xC0C0C0); + + /** + * Gray, or (0x80,0x80,0x80) in (R,G,B) + */ + public static final Color GRAY = fromRGB(0x808080); + + /** + * Black, or (0x00,0x00,0x00) in (R,G,B) + */ + public static final Color BLACK = fromRGB(0x000000); + + /** + * Red, or (0xFF,0x00,0x00) in (R,G,B) + */ + public static final Color RED = fromRGB(0xFF0000); + + /** + * Maroon, or (0x80,0x00,0x00) in (R,G,B) + */ + public static final Color MAROON = fromRGB(0x800000); + + /** + * Yellow, or (0xFF,0xFF,0x00) in (R,G,B) + */ + public static final Color YELLOW = fromRGB(0xFFFF00); + + /** + * Olive, or (0x80,0x80,0x00) in (R,G,B) + */ + public static final Color OLIVE = fromRGB(0x808000); + + /** + * Lime, or (0x00,0xFF,0x00) in (R,G,B) + */ + public static final Color LIME = fromRGB(0x00FF00); + + /** + * Green, or (0x00,0x80,0x00) in (R,G,B) + */ + public static final Color GREEN = fromRGB(0x008000); + + /** + * Aqua, or (0x00,0xFF,0xFF) in (R,G,B) + */ + public static final Color AQUA = fromRGB(0x00FFFF); + + /** + * Teal, or (0x00,0x80,0x80) in (R,G,B) + */ + public static final Color TEAL = fromRGB(0x008080); + + /** + * Blue, or (0x00,0x00,0xFF) in (R,G,B) + */ + public static final Color BLUE = fromRGB(0x0000FF); + + /** + * Navy, or (0x00,0x00,0x80) in (R,G,B) + */ + public static final Color NAVY = fromRGB(0x000080); + + /** + * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B) + */ + public static final Color FUCHSIA = fromRGB(0xFF00FF); + + /** + * Purple, or (0x80,0x00,0x80) in (R,G,B) + */ + public static final Color PURPLE = fromRGB(0x800080); + + /** + * Orange, or (0xFF,0xA5,0x00) in (R,G,B) + */ + public static final Color ORANGE = fromRGB(0xFFA500); + + private final byte red; + private final byte green; + private final byte blue; + + /** + * Creates a new Color object from a red, green, and blue + * + * @param red integer from 0-255 + * @param green integer from 0-255 + * @param blue integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly >255 or <0 + */ + public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new Color object from a blue, green, and red + * + * @param blue integer from 0-255 + * @param green integer from 0-255 + * @param red integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly >255 or <0 + */ + public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new color object from an integer that contains the red, green, and blue bytes in the lowest order 24 bits. + * + * @param rgb the integer storing the red, green, and blue values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 bits + */ + public static Color fromRGB(int rgb) throws IllegalArgumentException { + Validate.isTrue((rgb >> 24) == 0, "Extrenuous data in: ", rgb); + return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb >> 0 & BIT_MASK); + } + + /** + * Creates a new color object from an integer that contains the blue, green, and red bytes in the lowest order 24 bits. + * + * @param bgr the integer storing the blue, green, and red values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 bits + */ + public static Color fromBGR(int bgr) throws IllegalArgumentException { + Validate.isTrue((bgr >> 24) == 0, "Extrenuous data in: ", bgr); + return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr >> 0 & BIT_MASK); + } + + private Color(int red, int green, int blue) { + Validate.isTrue(red >= 0 && red <= BIT_MASK, "Red is not between 0-255: ", red); + Validate.isTrue(green >= 0 && green <= BIT_MASK, "Red is not between 0-255: ", green); + Validate.isTrue(blue >= 0 && blue <= BIT_MASK, "Red is not between 0-255: ", blue); + + this.red = (byte) red; + this.green = (byte) green; + this.blue = (byte) blue; + } + + /** + * Gets the red component + * + * @return red component, from 0 to 255 + */ + public int getRed() { + return BIT_MASK & red; + } + + /** + * Creates a new Color object with specified component + * + * @param red the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setRed(int red) { + return fromRGB(red, getGreen(), getBlue()); + } + + /** + * Gets the green component + * + * @return green component, from 0 to 255 + */ + public int getGreen() { + return BIT_MASK & green; + } + + /** + * Creates a new Color object with specified component + * + * @param green the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setGreen(int green) { + return fromRGB(getRed(), green, getBlue()); + } + + /** + * Gets the blue component + * + * @return blue component, from 0 to 255 + */ + public int getBlue() { + return BIT_MASK & blue; + } + + /** + * Creates a new Color object with specified component + * + * @param blue the red component, from 0 to 255 + * @return a new color object with the red component + */ + public Color setBlue(int blue) { + return fromRGB(getRed(), getGreen(), blue); + } + + /** + * + * @return An integer representation of this color, as 0xRRGGBB + */ + public int asRGB() { + return getRed() << 16 | getGreen() << 8 | getBlue() << 0; + } + + /** + * + * @return An integer representation of this color, as 0xBBGGRR + */ + public int asBGR() { + return getBlue() << 16 | getGreen() << 8 | getRed() << 0; + } + + /** + * Creates a new color with its RGB components changed as if it was dyed with the colors passed in, replicating + * vanilla workbench dyeing + * + * @param colors The DyeColors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + public Color mixDyes(DyeColor... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + Color[] toPass = new Color[colors.length]; + for (int i = 0; i < colors.length; i++) { + toPass[i] = colors[i].getColor(); + } + + return mixColors(toPass); + } + + /** + * Creates a new color with its RGB components changed as if it was dyed with the colors passed in, replicating + * vanilla workbench dyeing + * + * @param colors The colors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + public Color mixColors(Color... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + int totalRed = this.getRed(); + int totalGreen = this.getGreen(); + int totalBlue = this.getBlue(); + int totalMax = Math.max(Math.max(totalRed, totalGreen), totalBlue); + for (Color color : colors) { + totalRed += color.getRed(); + totalGreen += color.getGreen(); + totalBlue += color.getBlue(); + totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue()); + } + + float averageRed = totalRed / (colors.length + 1); + float averageGreen = totalGreen / (colors.length + 1); + float averageBlue = totalBlue / (colors.length + 1); + float averageMax = totalMax / (colors.length + 1); + + float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue); + float gainFactor = averageMax / maximumOfAverages; + + return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor)); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Color)) { + return false; + } + final Color that = (Color) o; + return this.blue == that.blue && this.green == that.green && this.red == that.red; + } + + @Override + public int hashCode() { + return asRGB() ^ Color.class.hashCode(); + } + + public Map<String, Object> serialize() { + return ImmutableMap.<String, Object>of( + "RED", getRed(), + "BLUE", getBlue(), + "GREEN", getGreen() + ); + } + + @SuppressWarnings("javadoc") + public static Color deserialize(Map<String, Object> map) { + return fromRGB( + asInt("RED", map), + asInt("GREEN", map), + asInt("BLUE", map) + ); + } + + private static int asInt(String string, Map<String, Object> map) { + Object value = map.get(string); + if (value == null) { + throw new IllegalArgumentException(string + " not in map " + map); + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException(string + '(' + value + ") is not a number"); + } + return ((Number) value).intValue(); + } + + @Override + public String toString() { + return "Color:[rgb0x" + Integer.toHexString(getRed()).toUpperCase() + Integer.toHexString(getGreen()).toUpperCase() + Integer.toHexString(getBlue()).toUpperCase() + "]"; + } +} diff --git a/src/main/java/org/bukkit/DyeColor.java b/src/main/java/org/bukkit/DyeColor.java index 531323fe..251b3321 100644 --- a/src/main/java/org/bukkit/DyeColor.java +++ b/src/main/java/org/bukkit/DyeColor.java @@ -12,73 +12,76 @@ public enum DyeColor { /** * Represents white dye */ - WHITE(0x0), + WHITE(0x0, Color.WHITE), /** * Represents orange dye */ - ORANGE(0x1), + ORANGE(0x1, Color.fromRGB(0xD87f33)), /** * Represents magenta dye */ - MAGENTA(0x2), + MAGENTA(0x2, Color.fromRGB(0xB24CD8)), /** * Represents light blue dye */ - LIGHT_BLUE(0x3), + LIGHT_BLUE(0x3, Color.fromRGB(0x6699D8)), /** * Represents yellow dye */ - YELLOW(0x4), + YELLOW(0x4, Color.fromRGB(0xE5E533)), /** * Represents lime dye */ - LIME(0x5), + LIME(0x5, Color.fromRGB(0x7FCC19)), /** * Represents pink dye */ - PINK(0x6), + PINK(0x6, Color.fromRGB(0xF27FA5)), /** * Represents gray dye */ - GRAY(0x7), + GRAY(0x7, Color.fromRGB(0x4C4C4C)), /** * Represents silver dye */ - SILVER(0x8), + SILVER(0x8, Color.fromRGB(0x999999)), /** * Represents cyan dye */ - CYAN(0x9), + CYAN(0x9, Color.fromRGB(0x4C7F99)), /** * Represents purple dye */ - PURPLE(0xA), + PURPLE(0xA, Color.fromRGB(0x7F3FB2)), /** * Represents blue dye */ - BLUE(0xB), + BLUE(0xB, Color.fromRGB(0x334CB2)), /** * Represents brown dye */ - BROWN(0xC), + BROWN(0xC, Color.fromRGB(0x664C33)), /** * Represents green dye */ - GREEN(0xD), + GREEN(0xD, Color.fromRGB(0x667F33)), /** * Represents red dye */ - RED(0xE), + RED(0xE, Color.fromRGB(0x993333)), /** * Represents black dye */ - BLACK(0xF); + BLACK(0xF, Color.fromRGB(0x191919)); private final byte data; + private final Color color; private final static Map<Byte, DyeColor> BY_DATA = Maps.newHashMap(); + private final static Map<Color, DyeColor> BY_COLOR = Maps.newHashMap(); - private DyeColor(final int data) { + private DyeColor(final int data, Color color) { this.data = (byte) data; + this.color = color; } /** @@ -91,6 +94,15 @@ public enum DyeColor { } /** + * Gets the color that this dye represents + * + * @return The {@link Color} that this dye represents + */ + public Color getColor() { + return color; + } + + /** * Gets the DyeColor with the given data value * * @param data Data value to fetch @@ -100,9 +112,20 @@ public enum DyeColor { return BY_DATA.get(data); } + /** + * Gets the DyeColor with the given color value + * + * @param color Color value to get the dye by + * @return The {@link DyeColor} representing the given value, or null if it doesn't exist + */ + public static DyeColor getByColor(final Color color) { + return BY_COLOR.get(color); + } + static { for (DyeColor color : values()) { BY_DATA.put(color.getData(), color); + BY_COLOR.put(color.getColor(), color); } } } diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java index 0488f471..020c1396 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java @@ -15,7 +15,7 @@ import com.google.common.collect.Maps; * An enum of all material ids accepted by the official server + client */ public enum Material { - AIR(0), + AIR(0, 0), STONE(1), GRASS(2), DIRT(3), diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java index a4f8ec29..ceec40cc 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -28,6 +28,8 @@ import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.scheduler.BukkitScheduler; import com.avaje.ebean.config.ServerConfig; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; /** * Represents a server implementation @@ -679,4 +681,12 @@ public interface Server extends PluginMessageRecipient { * @return The configured WarningState */ public WarningState getWarningState(); + + /** + * Gets the instance of the item factory (for {@link ItemMeta}). + * + * @return the item factory + * @see ItemFactory + */ + ItemFactory getItemFactory(); } diff --git a/src/main/java/org/bukkit/Utility.java b/src/main/java/org/bukkit/Utility.java new file mode 100644 index 00000000..0e54481b --- /dev/null +++ b/src/main/java/org/bukkit/Utility.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates a method (and sometimes constructor) will chain its internal operations. + * This is solely meant for identifying methods that don't need to be overridden / handled manually. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +public @interface Utility { +} diff --git a/src/main/java/org/bukkit/configuration/ConfigurationSection.java b/src/main/java/org/bukkit/configuration/ConfigurationSection.java index c5a2a4c3..60b53524 100644 --- a/src/main/java/org/bukkit/configuration/ConfigurationSection.java +++ b/src/main/java/org/bukkit/configuration/ConfigurationSection.java @@ -3,6 +3,8 @@ package org.bukkit.configuration; import java.util.Map; import java.util.Set; import java.util.List; + +import org.bukkit.Color; import org.bukkit.OfflinePlayer; import org.bukkit.util.Vector; import org.bukkit.inventory.ItemStack; @@ -665,6 +667,43 @@ public interface ConfigurationSection { public boolean isItemStack(String path); /** + * Gets the requested Color by path. + * <p /> + * If the Color does not exist but a default value has been specified, this + * will return the default value. If the Color does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the Color to get. + * @return Requested Color. + */ + public Color getColor(String path); + + /** + * Gets the requested {@link Color} by path, returning a default value if not found. + * <p /> + * If the Color does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the Color to get. + * @param def The default value to return if the path is not found or is not an Color. + * @return Requested Color. + */ + public Color getColor(String path, Color def); + + /** + * Checks if the specified path is a Color. + * <p /> + * If the path exists but is not a Color, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a Color and return + * appropriately. + * + * @param path Path of the Color to check. + * @return Whether or not the specified path is an Color. + */ + public boolean isColor(String path); + + /** * Gets the requested ConfigurationSection by path. * <p /> * If the ConfigurationSection does not exist but a default value has been specified, this diff --git a/src/main/java/org/bukkit/configuration/MemorySection.java b/src/main/java/org/bukkit/configuration/MemorySection.java index f4a9592e..ddf7c515 100644 --- a/src/main/java/org/bukkit/configuration/MemorySection.java +++ b/src/main/java/org/bukkit/configuration/MemorySection.java @@ -1,16 +1,19 @@ package org.bukkit.configuration; +import static org.bukkit.util.NumberConversions.*; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; + import org.apache.commons.lang.Validate; +import org.bukkit.Color; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; -import static org.bukkit.util.NumberConversions.*; /** * A type of {@link ConfigurationSection} that is stored in memory. @@ -653,6 +656,21 @@ public class MemorySection implements ConfigurationSection { return val instanceof ItemStack; } + public Color getColor(String path) { + Object def = getDefault(path); + return getColor(path, (def instanceof Color) ? (Color) def : null); + } + + public Color getColor(String path, Color def) { + Object val = get(path, def); + return (val instanceof Color) ? (Color) val : def; + } + + public boolean isColor(String path) { + Object val = get(path); + return val instanceof Color; + } + public ConfigurationSection getConfigurationSection(String path) { Object val = get(path, null); if (val != null) { diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java index abfc4b60..ca9f7b78 100644 --- a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java @@ -10,8 +10,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.Validate; +import org.bukkit.Color; import org.bukkit.configuration.Configuration; import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; @@ -27,6 +29,8 @@ public class ConfigurationSerialization { registerClass(Vector.class); registerClass(BlockVector.class); registerClass(ItemStack.class); + registerClass(Color.class); + registerClass(PotionEffect.class); } protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) { diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java new file mode 100644 index 00000000..c6654c4a --- /dev/null +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -0,0 +1,82 @@ +package org.bukkit.inventory; + +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +/** + * An instance of the ItemFactory can be obtained with {@link Server#getItemFactory()}. + * The ItemFactory is solely responsible for creating item meta containers to apply on item stacks. + */ +public interface ItemFactory { + + /** + * This creates a new item meta for the material. + * @param material The material to consider as base for the meta + * @return a new ItemMeta that could be applied to an item stack of the specified material + */ + ItemMeta getItemMeta(final Material material); + + /** + * This method checks the item meta to confirm that it is applicable (no data lost if applied) to the specified ItemStack. + * A {@link SkullMeta} would not be valid for a sword, but a normal {@link ItemMeta} from an enchanted dirt block would. + * @param meta Meta to check + * @param stack Item that meta will be applied to + * @return true if the meta can be applied without losing data, false otherwise + * @throws IllegalArgumentException if the meta was not created by this factory + */ + boolean isApplicable(final ItemMeta meta, final ItemStack stack) throws IllegalArgumentException; + + /** + * This method checks the item meta to confirm that it is applicable (no data lost if applied) to the specified Material. + * A {@link SkullMeta} would not be valid for a sword, but a normal {@link ItemMeta} from an enchanted dirt block would. + * @param meta Meta to check + * @param material Material that meta will be applied to + * @return true if the meta can be applied without losing data, false otherwise + * @throws IllegalArgumentException if the meta was not created by this factory + */ + boolean isApplicable(final ItemMeta meta, final Material material) throws IllegalArgumentException; + + /** + * This method is used to compare two item meta data objects. + * @param meta1 First meta to compare, and may be null to indicate no data + * @param meta2 Second meta to compare, and may be null to indicate no data + * @return false if one of the meta has data the other does not, otherwise true + * @throws IllegalArgumentException if either meta was not created by this factory + */ + boolean equals(final ItemMeta meta1, final ItemMeta meta2) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified stack. + * The item meta returned will always be a valid meta for a given item stack of the specified material. + * It may be a more or less specific meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. <br> + * <br> + * Example, if a {@link SkullMeta} is being applied to a book, this method would return a {@link BookMeta} containing all + * information in the specified meta that is applicable to an {@link ItemMeta}, the highest common interface. + * + * @param meta the meta to convert + * @param stack the stack to convert the meta for + * @return An appropriate item meta for the specified item stack. No guarantees are made as to if a copy is returned. This will be null for a stack of air. + * @throws IllegalArgumentException if the specified meta was not created by this factory + */ + ItemMeta asMetaFor(final ItemMeta meta, final ItemStack stack) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified material. + * The item meta returned will always be a valid meta for a given item stack of the specified material. + * It may be a more or less specific meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. <br> + * <br> + * Example, if a {@link SkullMeta} is being applied to a book, this method would return a {@link BookMeta} containing all + * information in the specified meta that is applicable to an {@link ItemMeta}, the highest common interface. + * + * @param meta the meta to convert + * @param material the material to convert the meta for + * @return An appropriate item meta for the specified item material. No guarantees are made as to if a copy is returned. This will be null for air. + * @throws IllegalArgumentException if the specified meta was not created by this factory + */ + ItemMeta asMetaFor(final ItemMeta meta, final Material material) throws IllegalArgumentException; +} diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java index db7c5005..58b7e992 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1,48 +1,97 @@ package org.bukkit.inventory; import com.google.common.collect.ImmutableMap; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.Utility; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.material.MaterialData; /** * Represents a stack of items */ public class ItemStack implements Cloneable, ConfigurationSerializable { - private int type; + private int type = 0; private int amount = 0; private MaterialData data = null; private short durability = 0; - private Map<Enchantment, Integer> enchantments = new HashMap<Enchantment, Integer>(); + private ItemMeta meta; + + @Utility + protected ItemStack() {} + /** + * Defaults stack size to 1, with no extra data + * + * @param type item material id + */ public ItemStack(final int type) { this(type, 1); } + /** + * Defaults stack size to 1, with no extra data + * + * @param type item material + */ public ItemStack(final Material type) { this(type, 1); } + /** + * An item stack with no extra data + * + * @param type item material id + * @param amount stack size + */ public ItemStack(final int type, final int amount) { this(type, amount, (short) 0); } + /** + * An item stack with no extra data + * + * @param type item material + * @param amount stack size + */ public ItemStack(final Material type, final int amount) { this(type.getId(), amount); } + /** + * An item stack with the specified damage / durability + * + * @param type item material id + * @param amount stack size + * @param damage durability / damage + */ public ItemStack(final int type, final int amount, final short damage) { - this(type, amount, damage, null); + this.type = type; + this.amount = amount; + this.durability = damage; } + /** + * An item stack with the specified damage / durabiltiy + * + * @param type item material + * @param amount stack size + * @param damage durability / damage + */ public ItemStack(final Material type, final int amount, final short damage) { this(type.getId(), amount, damage); } + /** + * @deprecated this method uses an ambiguous data byte object + */ + @Deprecated public ItemStack(final int type, final int amount, final short damage, final Byte data) { this.type = type; this.amount = amount; @@ -53,18 +102,29 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { } } + /** + * @deprecated this method uses an ambiguous data byte object + */ + @Deprecated public ItemStack(final Material type, final int amount, final short damage, final Byte data) { this(type.getId(), amount, damage, data); } - public ItemStack(final ItemStack stack) { - this.type = stack.type; - this.amount = stack.amount; - this.durability = stack.durability; - if (stack.data != null) { - this.data = stack.data.clone(); + /** + * Creates a new item stack derived from the specified stack + * + * @param stack the stack to copy + * @throws IllegalArgumentException if the specified stack is null or returns an item meta not created by the item factory + */ + public ItemStack(final ItemStack stack) throws IllegalArgumentException { + Validate.notNull(stack, "Cannot copy null stack"); + this.type = stack.getTypeId(); + this.amount = stack.getAmount(); + this.durability = stack.getDurability(); + this.data = stack.getData(); + if (stack.hasItemMeta()) { + setItemMeta0(stack.getItemMeta(), getType0()); } - this.addUnsafeEnchantments(stack.getEnchantments()); } /** @@ -72,8 +132,18 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * * @return Type of the items in this stack */ + @Utility public Material getType() { - return Material.getMaterial(type); + return getType0(getTypeId()); + } + + private Material getType0() { + return getType0(this.type); + } + + private static Material getType0(int id) { + Material material = Material.getMaterial(id); + return material == null ? Material.AIR : material; } /** @@ -83,7 +153,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * * @param type New type to set the items in this stack to */ + @Utility public void setType(Material type) { + Validate.notNull(type, "Material cannot be null"); setTypeId(type.getId()); } @@ -105,6 +177,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { */ public void setTypeId(int type) { this.type = type; + if (this.meta != null) { + this.meta = Bukkit.getItemFactory().asMetaFor(meta, getType0()); + } createData((byte) 0); } @@ -132,9 +207,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @return MaterialData for this item */ public MaterialData getData() { - Material mat = Material.getMaterial(getTypeId()); - if (mat != null && mat.getData() != null) { - data = mat.getNewData((byte) this.durability); + Material mat = getType(); + if (data == null && mat != null && mat.getData() != null) { + data = mat.getNewData((byte) this.getDurability()); } return data; @@ -148,7 +223,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { public void setData(MaterialData data) { Material mat = getType(); - if ((mat == null) || (mat.getData() == null)) { + if (data == null || mat == null || mat.getData() == null) { this.data = data; } else { if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) { @@ -183,12 +258,12 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * * @return The maximum you can stack this material to. */ + @Utility public int getMaxStackSize() { Material material = getType(); if (material != null) { return material.getMaxStackSize(); } - return -1; } @@ -203,19 +278,44 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { } @Override + @Utility public String toString() { - return "ItemStack{" + getType().name() + " x " + getAmount() + "}"; + StringBuilder toString = new StringBuilder("ItemStack{").append(getType().name()).append(" x ").append(getAmount()); + if (hasItemMeta()) { + toString.append(", ").append(getItemMeta()); + } + return toString.append('}').toString(); } @Override + @Utility public boolean equals(Object obj) { + if (this == obj) { + return true; + } if (!(obj instanceof ItemStack)) { return false; } - ItemStack item = (ItemStack) obj; + ItemStack stack = (ItemStack) obj; + return getAmount() == stack.getAmount() && isSimilar(stack); + } - return item.getAmount() == getAmount() && item.getTypeId() == getTypeId() && getDurability() == item.getDurability() && getEnchantments().equals(item.getEnchantments()); + /** + * This method is the same as equals, but does not consider stack size (amount). + * + * @param stack the item stack to compare to + * @return true if the two stacks are equal, ignoring the amount + */ + @Utility + public boolean isSimilar(ItemStack stack) { + if (stack == null) { + return false; + } + if (stack == this) { + return true; + } + return getTypeId() == stack.getTypeId() && getDurability() == stack.getDurability() && hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true); } @Override @@ -223,7 +323,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { try { ItemStack itemStack = (ItemStack) super.clone(); - itemStack.enchantments = new HashMap<Enchantment, Integer>(this.enchantments); + if (this.meta != null) { + itemStack.meta = this.meta.clone(); + } + if (this.data != null) { itemStack.data = this.data.clone(); } @@ -235,11 +338,15 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { } @Override - public int hashCode() { - int hash = 11; + @Utility + public final int hashCode() { + int hash = 1; + + hash = hash * 31 + getTypeId(); + hash = hash * 31 + getAmount(); + hash = hash * 31 + (getDurability() & 0xffff); + hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0); - hash = hash * 19 + 7 * getTypeId(); // Overriding hashCode since equals is overridden, it's just - hash = hash * 7 + 23 * getAmount(); // too bad these are mutable values... Q_Q return hash; } @@ -250,7 +357,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @return True if this has the given enchantment */ public boolean containsEnchantment(Enchantment ench) { - return enchantments.containsKey(ench); + return meta == null ? false : meta.hasEnchant(ench); } /** @@ -260,7 +367,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @return Level of the enchantment, or 0 */ public int getEnchantmentLevel(Enchantment ench) { - return enchantments.get(ench); + return meta == null ? 0 : meta.getEnchantLevel(ench); } /** @@ -269,7 +376,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @return Map of enchantments. */ public Map<Enchantment, Integer> getEnchantments() { - return ImmutableMap.copyOf(enchantments); + return meta == null ? ImmutableMap.<Enchantment, Integer>of() : meta.getEnchants(); } /** @@ -279,8 +386,13 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * for each element of the map. * * @param enchantments Enchantments to add + * @throws IllegalArgumentException if the specified enchantments is null + * @throws IllegalArgumentException if any specific enchantment or level is null. + * <b>Warning</b>: Some enchantments may be added before this exception is thrown. */ + @Utility public void addEnchantments(Map<Enchantment, Integer> enchantments) { + Validate.notNull(enchantments, "Enchantments cannot be null"); for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) { addEnchantment(entry.getKey(), entry.getValue()); } @@ -293,8 +405,11 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * * @param ench Enchantment to add * @param level Level of the enchantment + * @throws IllegalArgumentException if enchantment null, or enchantment is not applicable */ + @Utility public void addEnchantment(Enchantment ench, int level) { + Validate.notNull(ench, "Enchantment cannot be null"); if ((level < ench.getStartLevel()) || (level > ench.getMaxLevel())) { throw new IllegalArgumentException("Enchantment level is either too low or too high (given " + level + ", bounds are " + ench.getStartLevel() + " to " + ench.getMaxLevel()); } else if (!ench.canEnchantItem(this)) { @@ -312,6 +427,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * * @param enchantments Enchantments to add */ + @Utility public void addUnsafeEnchantments(Map<Enchantment, Integer> enchantments) { for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) { addUnsafeEnchantment(entry.getKey(), entry.getValue()); @@ -330,7 +446,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @param level Level of the enchantment */ public void addUnsafeEnchantment(Enchantment ench, int level) { - enchantments.put(ench, level); + (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(getType0()) : meta).addEnchant(ench, level, true); } /** @@ -340,38 +456,43 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { * @return Previous level, or 0 */ public int removeEnchantment(Enchantment ench) { - Integer previous = enchantments.remove(ench); - return (previous == null) ? 0 : previous; + int level = getEnchantmentLevel(ench); + if (level == 0 || meta == null) { + return level; + } + meta.removeEnchant(ench); + return level; } + @Utility public Map<String, Object> serialize() { Map<String, Object> result = new LinkedHashMap<String, Object>(); result.put("type", getType().name()); - if (durability != 0) { - result.put("damage", durability); + if (getDurability() != 0) { + result.put("damage", getDurability()); } - if (amount != 1) { - result.put("amount", amount); + if (getAmount() != 1) { + result.put("amount", getAmount()); } - Map<Enchantment, Integer> enchants = getEnchantments(); - - if (enchants.size() > 0) { - Map<String, Integer> safeEnchants = new HashMap<String, Integer>(); - - for (Map.Entry<Enchantment, Integer> entry : enchants.entrySet()) { - safeEnchants.put(entry.getKey().getName(), entry.getValue()); - } - - result.put("enchantments", safeEnchants); + ItemMeta meta = getItemMeta(); + if (!Bukkit.getItemFactory().equals(meta, null)) { + result.put("meta", meta); } return result; } + /** + * Required method for configuration serialization + * + * @param args map to deserialize + * @return deserialized item stack + * @see ConfigurationSerializable + */ public static ItemStack deserialize(Map<String, Object> args) { Material type = Material.getMaterial((String) args.get("type")); short damage = 0; @@ -387,7 +508,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { ItemStack result = new ItemStack(type, amount, damage); - if (args.containsKey("enchantments")) { + if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated Object raw = args.get("enchantments"); if (raw instanceof Map) { @@ -401,8 +522,61 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { } } } + } else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist + Object raw = args.get("meta"); + if (raw instanceof ItemMeta) { + result.setItemMeta((ItemMeta) raw); + } } return result; } + + /** + * Get a copy of this ItemStack's {@link ItemMeta}. + * + * @return a copy of the current ItemStack's ItemData + */ + public ItemMeta getItemMeta() { + return this.meta == null ? Bukkit.getItemFactory().getItemMeta(getType0()) : this.meta.clone(); + } + + /** + * Checks to see if any meta data has been defined. + * + * @return Returns true if some meta data has been set for this item + */ + public boolean hasItemMeta() { + return !Bukkit.getItemFactory().equals(meta, null); + } + + /** + * Set the ItemMeta of this ItemStack. + * + * @param itemMeta new ItemMeta, or null to indicate meta data be cleared. + * @return True if successfully applied ItemMeta, see {@link ItemFactory#isApplicable(ItemMeta, ItemStack)} + * @throws IllegalArgumentException if the item meta was not created by the {@link ItemFactory} + */ + public boolean setItemMeta(ItemMeta itemMeta) { + return setItemMeta0(itemMeta, getType0()); + } + + /* + * Cannot be overridden, so it's safe for constructor call + */ + private boolean setItemMeta0(ItemMeta itemMeta, Material material) { + if (itemMeta == null) { + this.meta = null; + return true; + } + if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) { + return false; + } + this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material); + if (this.meta == itemMeta) { + this.meta = itemMeta.clone(); + } + + return true; + } } diff --git a/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/src/main/java/org/bukkit/inventory/meta/BookMeta.java new file mode 100644 index 00000000..987a2866 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java @@ -0,0 +1,114 @@ +package org.bukkit.inventory.meta; + +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents a book ({@link Material#BOOK_AND_QUILL} or {@link Material#WRITTEN_BOOK}) that can have a title, an author, and pages. + */ +public interface BookMeta extends ItemMeta { + + /** + * Checks for the existence of a title in the book. + * + * @return true if the book has a title + */ + boolean hasTitle(); + + /** + * Gets the title of the book. + * + * @return the title of the book + */ + String getTitle(); + + /** + * Sets the title of the book. Limited to 16 characters. + * + * @param title the title to set + * @return true if the title was successfully set + */ + boolean setTitle(String title); + + /** + * Checks for the existence of an author in the book. + * + * @return the author of the book + */ + boolean hasAuthor(); + + /** + * Gets the author of the book. + * + * @return the author of the book + */ + String getAuthor(); + + /** + * Sets the author of the book. + * + * @param author the author of the book + */ + void setAuthor(String author); + + /** + * Checks for the existence of pages in the book. + * + * @return true if the book has pages + */ + boolean hasPages(); + + /** + * Gets the specified page in the book. + * + * @param page the page number to get + * @return the page from the book + */ + String getPage(int page); + + /** + * Sets the specified page in the book. + * + * @param page the page number to set + * @param data the data to set for that page + */ + void setPage(int page, String data); + + /** + * Gets all the pages in the book. + * + * @return list of all the pages in the book + */ + List<String> getPages(); + + /** + * Clears the existing book pages, and sets the book to use the provided pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of pages to set the book to use + */ + void setPages(List<String> pages); + + /** + * Clears the existing book pages, and sets the book to use the provided pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of strings, each being a page + */ + void setPages(String... pages); + + /** + * Adds new pages to the end of the book. + * + * @param pages A list of strings, each being a page + */ + void addPage(String... pages); + + /** + * Gets the number of pages in the book. + * + * @return the number of pages in the book + */ + int getPageCount(); + + BookMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java new file mode 100644 index 00000000..fe744546 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java @@ -0,0 +1,108 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import java.util.Map; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.enchantments.Enchantment; + +/** + * This type represents the storage mechanism for auxiliary item data. + * An implementation will handle the creation and application for ItemMeta. + * This class should not be implemented by a plugin in a live environment. + */ +public interface ItemMeta extends Cloneable, ConfigurationSerializable { + + /** + * Checks for existence of a display name + * + * @return true if this has a display name + */ + boolean hasDisplayName(); + + /** + * Gets the display name that is set + * + * @return the display name that is set + */ + String getDisplayName(); + + /** + * Sets the display name + * + * @param name the name to set + */ + void setDisplayName(String name); + + /** + * Checks for existence of lore + * + * @return true if this has lore + */ + boolean hasLore(); + + /** + * Gets the lore that is set + * + * @return a list of lore that is set + */ + List<String> getLore(); + + /** + * Sets the lore for this item + * + * @param lore the lore that will be set + */ + void setLore(List<String> lore); + + /** + * Checks for the existence of any enchantments + * + * @return true if an enchantment exists on this meta + */ + boolean hasEnchants(); + + /** + * Checks for existence of the specified enchantment + * + * @param ench enchantment to check + * @return true if this enchantment exists for this meta + */ + boolean hasEnchant(Enchantment ench); + + /** + * Checks for the level of the specified enchantment + * + * @param ench enchantment to check + * @return The level that the specified enchantment has, or 0 if none + */ + int getEnchantLevel(Enchantment ench); + + /** + * This method gets a copy the enchantments in this ItemMeta + * + * @return An immutable copy of the enchantments + */ + Map<Enchantment, Integer> getEnchants(); + + /** + * This method adds the specified enchantment to this item meta + * + * @param ench Enchantment to add + * @param level Level for the enchantment + * @param ignoreLevelRestriction this indicates the enchantment should be applied, ignoring the level limit + * @return true if the item meta changed as a result of this call, false otherwise + */ + boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * This method removes the specified enchantment from this item meta + * + * @param ench Enchantment to remove + * @return true if the item meta changed as a result of this call, false otherwise + */ + boolean removeEnchant(Enchantment ench); + + @SuppressWarnings("javadoc") + ItemMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java b/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java new file mode 100644 index 00000000..2ca6b6ec --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java @@ -0,0 +1,26 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.Material; + +/** + * Represents leather armor ({@link Material#LEATHER_BOOTS}, {@link Material#LEATHER_CHESTPLATE}, {@link Material#LEATHER_HELMET}, or {@link Material#LEATHER_LEGGINGS}) that can be colored. + */ +public interface LeatherArmorMeta extends ItemMeta { + + /** + * Gets the color of the armor + * + * @return the color of the armor, never null + */ + Color getColor(); + + /** + * Sets the color of the armor + * + * @param color the color to set, null makes it the default leather color + */ + void setColor(Color color); + + LeatherArmorMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/MapMeta.java b/src/main/java/org/bukkit/inventory/meta/MapMeta.java new file mode 100644 index 00000000..6ff13671 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/MapMeta.java @@ -0,0 +1,23 @@ +package org.bukkit.inventory.meta; + +/** + * Represents a map that can be scalable. + */ +public interface MapMeta extends ItemMeta { + + /** + * Checks to see if this map is scaling + * + * @return true if this map is scaling + */ + boolean isScaling(); + + /** + * Sets if this map is scaling or not + * + * @param value true to scale + */ + void setScaling(boolean value); + + MapMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/PotionMeta.java b/src/main/java/org/bukkit/inventory/meta/PotionMeta.java new file mode 100644 index 00000000..dad8fc30 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/PotionMeta.java @@ -0,0 +1,69 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Material; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.List; + +/** + * Represents a potion ({@link Material#POTION}) that can have custom effects. + */ +public interface PotionMeta extends ItemMeta { + + /** + * Checks for the presence of custom potion effects + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to this potion + * + * @return the immutable list of custom potion effects + */ + List<PotionEffect> getCustomEffects(); + + /** + * Adds a custom potion effect to this potion + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be overwritten + * @return true if the potion meta changed as a result of this call + */ + boolean addCustomEffect(PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this potion + * + * @param type the potion effect type to remove + * @return true if the potion meta changed as a result of this call + */ + boolean removeCustomEffect(PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this potion + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(PotionEffectType type); + + /** + * Moves a potion effect to the top of the potion effect list. + * This causes the client to display the potion effect in the potion's name. + * + * @param type the potion effect type to move + * @return true if the potion meta changed as a result of this call + */ + boolean setMainEffect(PotionEffectType type); + + /** + * Removes all custom potion effects from this potion + * + * @return true if the potion meta changed as a result of this call + */ + boolean clearCustomEffects(); + + PotionMeta clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/Repairable.java b/src/main/java/org/bukkit/inventory/meta/Repairable.java new file mode 100644 index 00000000..c49844ed --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/Repairable.java @@ -0,0 +1,31 @@ +package org.bukkit.inventory.meta; + +/** + * Represents an item that can be repaired at an anvil. + */ +public interface Repairable { + + /** + * Checks to see if this has a repair penalty + * + * @return true if this has a repair penalty + */ + boolean hasRepairCost(); + + /** + * Gets the repair penalty + * + * @return the repair penalty + */ + int getRepairCost(); + + /** + * Sets the repair penalty + * + * @param cost repair penalty + */ + void setRepairCost(int cost); + + @SuppressWarnings("javadoc") + Repairable clone(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/SkullMeta.java b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java new file mode 100644 index 00000000..42598576 --- /dev/null +++ b/src/main/java/org/bukkit/inventory/meta/SkullMeta.java @@ -0,0 +1,33 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Material; + +/** + * Represents a skull ({@link Material#SKULL_ITEM}) that can have an owner. + */ +public interface SkullMeta extends ItemMeta { + + /** + * Gets the owner of the skull + * + * @return the owner if the skull + */ + String getOwner(); + + /** + * Checks to see if the skull has an owner + * + * @return true if the skull has an owner + */ + boolean hasOwner(); + + /** + * Sets the owner of the skull + * + * @param owner the new owner of the skull + * @return true if the owner was successfully set + */ + boolean setOwner(String owner); + + SkullMeta clone(); +} diff --git a/src/main/java/org/bukkit/potion/PotionEffect.java b/src/main/java/org/bukkit/potion/PotionEffect.java index 6c738776..5182a074 100644 --- a/src/main/java/org/bukkit/potion/PotionEffect.java +++ b/src/main/java/org/bukkit/potion/PotionEffect.java @@ -1,24 +1,101 @@ package org.bukkit.potion; +import java.util.Map; +import java.util.NoSuchElementException; + import org.apache.commons.lang.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; import org.bukkit.entity.LivingEntity; +import com.google.common.collect.ImmutableMap; + /** * Represents a potion effect, that can be added to a {@link LivingEntity}. A * potion effect has a duration that it will last for, an amplifier that will * enhance its effects, and a {@link PotionEffectType}, that represents its * effect on an entity. */ -public class PotionEffect { +@SerializableAs("PotionEffect") +public class PotionEffect implements ConfigurationSerializable { + private static final String AMPLIFIER = "amplifier"; + private static final String DURATION = "duration"; + private static final String TYPE = "effect"; + private static final String AMBIENT = "ambient"; private final int amplifier; private final int duration; private final PotionEffectType type; + private final boolean ambient; - public PotionEffect(PotionEffectType type, int duration, int amplifier) { + /** + * Creates a potion effect. + * + * @param type effect type + * @param duration measured in ticks, see {@link PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier, boolean ambient) { Validate.notNull(type, "effect type cannot be null"); this.type = type; this.duration = duration; this.amplifier = amplifier; + this.ambient = ambient; + } + + /** + * Creates a potion affect. Assumes ambient is true. + * + * @param type Effect type + * @param duration measured in ticks + * @param amplifier the amplifier for the affect + * @see PotionEffect#PotionEffect(PotionEffectType, int, int, boolean) + */ + public PotionEffect(PotionEffectType type, int duration, int amplifier) { + this(type, duration, amplifier, true); + } + + /** + * Constructor for deserialization. + * + * @param map the map to deserialize from + */ + public PotionEffect(Map<String, Object> map) { + this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT)); + } + + private static PotionEffectType getEffectType(Map<?,?> map) { + int type = getInt(map, TYPE); + PotionEffectType effect = PotionEffectType.getById(type); + if (effect != null) { + return effect; + } + throw new NoSuchElementException(map + " does not contain " + TYPE); + } + + private static int getInt(Map<?,?> map, Object key) { + Object num = map.get(key); + if (num instanceof Integer) { + return (Integer) num; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + private static boolean getBool(Map<?,?> map, Object key) { + Object bool = map.get(key); + if (bool instanceof Boolean) { + return (Boolean) bool; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + public Map<String, Object> serialize() { + return ImmutableMap.<String, Object>of( + TYPE, type.getId(), + DURATION, duration, + AMPLIFIER, amplifier, + AMBIENT, ambient + ); } /** @@ -38,18 +115,11 @@ public class PotionEffect { if (this == obj) { return true; } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - PotionEffect other = (PotionEffect) obj; - if (type == null) { - if (other.type != null) { - return false; - } - } else if (!type.equals(other.type)) { + if (!(obj instanceof PotionEffect)) { return false; } - return true; + PotionEffect that = (PotionEffect) obj; + return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration; } /** @@ -80,8 +150,27 @@ public class PotionEffect { return type; } + /** + * Makes potion effect produce more, translucent, particles. + * + * @return if this effect is ambient + */ + public boolean isAmbient() { + return ambient; + } + @Override public int hashCode() { - return 31 + ((type == null) ? 0 : type.hashCode()); - }; + int hash = 1; + hash = hash * 31 + type.hashCode(); + hash = hash * 31 + amplifier; + hash = hash * 31 + duration; + hash ^= 0x22222222 >> (ambient ? 1 : -1); + return hash; + } + + @Override + public String toString() { + return type.getName() + (ambient ? ":(" : ":") + duration + "t-x" + amplifier + (ambient ? ")" : ""); + } } diff --git a/src/main/java/org/bukkit/potion/PotionEffectType.java b/src/main/java/org/bukkit/potion/PotionEffectType.java index 626987a9..b6e8a0de 100644 --- a/src/main/java/org/bukkit/potion/PotionEffectType.java +++ b/src/main/java/org/bukkit/potion/PotionEffectType.java @@ -115,6 +115,14 @@ public abstract class PotionEffectType { this.id = id; } + /** + * Creates a PotionEffect from this PotionEffectType, applying duration modifiers and checks. + * + * @see PotionBrewer#createEffect(PotionEffectType, int, int) + * @param duration time in ticks + * @param amplifier the effect's amplifier + * @return a resulting potion effect + */ public PotionEffect createEffect(int duration, int amplifier) { return Potion.getBrewer().createEffect(this, duration, amplifier); } diff --git a/src/test/java/org/bukkit/ColorTest.java b/src/test/java/org/bukkit/ColorTest.java new file mode 100644 index 00000000..8d9557af --- /dev/null +++ b/src/test/java/org/bukkit/ColorTest.java @@ -0,0 +1,365 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class ColorTest { + static class TestColor { + static int id = 0; + final String name; + final int rgb; + final int bgr; + final int r; + final int g; + final int b; + + TestColor(int rgb, int bgr, int r, int g, int b) { + this.rgb = rgb; + this.bgr = bgr; + this.r = r; + this.g = g; + this.b = b; + this.name = id + ":" + Integer.toHexString(rgb).toUpperCase() + "_" + Integer.toHexString(bgr).toUpperCase() + "-r" + Integer.toHexString(r).toUpperCase() + "-g" + Integer.toHexString(g).toUpperCase() + "-b" + Integer.toHexString(b).toUpperCase(); + } + } + + static TestColor[] examples = new TestColor[] { + /* 0xRRGGBB, 0xBBGGRR, 0xRR, 0xGG, 0xBB */ + new TestColor(0xFFFFFF, 0xFFFFFF, 0xFF, 0xFF, 0xFF), + new TestColor(0xFFFFAA, 0xAAFFFF, 0xFF, 0xFF, 0xAA), + new TestColor(0xFF00FF, 0xFF00FF, 0xFF, 0x00, 0xFF), + new TestColor(0x67FF22, 0x22FF67, 0x67, 0xFF, 0x22), + new TestColor(0x000000, 0x000000, 0x00, 0x00, 0x00) + }; + + @Test + public void testSerialization() throws Throwable { + for (TestColor testColor : examples) { + Color base = Color.fromRGB(testColor.rgb); + + YamlConfiguration toSerialize = new YamlConfiguration(); + toSerialize.set("color", base); + String serialized = toSerialize.saveToString(); + + YamlConfiguration deserialized = new YamlConfiguration(); + deserialized.loadFromString(serialized); + + assertThat(testColor.name + " on " + serialized, base, is(deserialized.getColor("color"))); + } + } + + // Equality tests + @Test + public void testEqualities() { + for (TestColor testColor : examples) { + Color fromRGB = Color.fromRGB(testColor.rgb); + Color fromBGR = Color.fromBGR(testColor.bgr); + Color fromRGBs = Color.fromRGB(testColor.r, testColor.g, testColor.b); + Color fromBGRs = Color.fromBGR(testColor.b, testColor.g, testColor.r); + + assertThat(testColor.name, fromRGB, is(fromRGBs)); + assertThat(testColor.name, fromRGB, is(fromBGR)); + assertThat(testColor.name, fromRGB, is(fromBGRs)); + assertThat(testColor.name, fromRGBs, is(fromBGR)); + assertThat(testColor.name, fromRGBs, is(fromBGRs)); + assertThat(testColor.name, fromBGR, is(fromBGRs)); + } + } + + @Test + public void testInequalities() { + for (int i = 1; i < examples.length; i++) { + TestColor testFrom = examples[i]; + Color from = Color.fromRGB(testFrom.rgb); + for (int j = i - 1; j >= 0; j--) { + TestColor testTo = examples[j]; + Color to = Color.fromRGB(testTo.rgb); + String name = testFrom.name + " to " + testTo.name; + assertThat(name, from, is(not(to))); + + Color transform = from.setRed(testTo.r).setBlue(testTo.b).setGreen(testTo.g); + assertThat(name, transform, is(not(sameInstance(from)))); + assertThat(name, transform, is(to)); + } + } + } + + // RGB tests + @Test + public void testRGB() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).asRGB(), is(testColor.rgb)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB1() { + Color.fromRGB(0x01000000); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB2() { + Color.fromRGB(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB3() { + Color.fromRGB(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB4() { + Color.fromRGB(-1); + } + + // BGR tests + @Test + public void testBGR() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).asBGR(), is(testColor.bgr)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR1() { + Color.fromBGR(0x01000000); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR2() { + Color.fromBGR(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR3() { + Color.fromBGR(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR4() { + Color.fromBGR(-1); + } + + // Red tests + @Test + public void testRed() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getRed(), is(testColor.r)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR01() { + Color.fromRGB(-1, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR02() { + Color.fromRGB(Integer.MAX_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR03() { + Color.fromRGB(Integer.MIN_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR04() { + Color.fromRGB(0x100, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR05() { + Color.fromBGR(0x00, 0x00, -1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR06() { + Color.fromBGR(0x00, 0x00, Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR07() { + Color.fromBGR(0x00, 0x00, Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR08() { + Color.fromBGR(0x00, 0x00, 0x100); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR09() { + Color.WHITE.setRed(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR10() { + Color.WHITE.setRed(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR11() { + Color.WHITE.setRed(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR12() { + Color.WHITE.setRed(0x100); + } + + // Blue tests + @Test + public void testBlue() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getBlue(), is(testColor.b)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB01() { + Color.fromRGB(0x00, 0x00, -1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB02() { + Color.fromRGB(0x00, 0x00, Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB03() { + Color.fromRGB(0x00, 0x00, Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB04() { + Color.fromRGB(0x00, 0x00, 0x100); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB05() { + Color.fromBGR(-1, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB06() { + Color.fromBGR(Integer.MAX_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB07() { + Color.fromBGR(Integer.MIN_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB08() { + Color.fromBGR(0x100, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB09() { + Color.WHITE.setBlue(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB10() { + Color.WHITE.setBlue(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB11() { + Color.WHITE.setBlue(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB12() { + Color.WHITE.setBlue(0x100); + } + + // Green tests + @Test + public void testGreen() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getGreen(), is(testColor.g)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG01() { + Color.fromRGB(0x00, -1, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG02() { + Color.fromRGB(0x00, Integer.MAX_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG03() { + Color.fromRGB(0x00, Integer.MIN_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG04() { + Color.fromRGB(0x00, 0x100, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG05() { + Color.fromBGR(0x00, -1, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG06() { + Color.fromBGR(0x00, Integer.MAX_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG07() { + Color.fromBGR(0x00, Integer.MIN_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG08() { + Color.fromBGR(0x00, 0x100, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG09() { + Color.WHITE.setGreen(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG10() { + Color.WHITE.setGreen(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG11() { + Color.WHITE.setGreen(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG12() { + Color.WHITE.setGreen(0x100); + } +} diff --git a/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java index 4e3478fd..6dab4779 100644 --- a/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java +++ b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java @@ -403,7 +403,7 @@ public abstract class ConfigurationSectionTest { map.put("two", "two"); map.put("three", 3.14); - List<Object> value = Arrays.asList((Object) "One", "Two", "Three", 4, "5", 6.0, true, "false", map); + List<Object> value = Arrays.asList("One", "Two", "Three", 4, "5", 6.0, true, "false", map); section.set(key, value); diff --git a/src/test/java/org/bukkit/configuration/file/TestEnchantment.java b/src/test/java/org/bukkit/configuration/file/TestEnchantment.java deleted file mode 100644 index 93569721..00000000 --- a/src/test/java/org/bukkit/configuration/file/TestEnchantment.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.bukkit.configuration.file;
-
-import org.bukkit.enchantments.Enchantment;
-import org.bukkit.enchantments.EnchantmentTarget;
-import org.bukkit.inventory.ItemStack;
-
-public class TestEnchantment extends Enchantment {
-
- public static void registerEnchantments() {
- Enchantment.registerEnchantment(new TestEnchantment(0, "DUMMY_0"));
- Enchantment.registerEnchantment(new TestEnchantment(1, "DUMMY_1"));
- Enchantment.registerEnchantment(new TestEnchantment(2, "DUMMY_2"));
- Enchantment.registerEnchantment(new TestEnchantment(3, "DUMMY_3"));
- Enchantment.registerEnchantment(new TestEnchantment(4, "DUMMY_4"));
- Enchantment.registerEnchantment(new TestEnchantment(5, "DUMMY_5"));
- }
-
- private final String name;
-
- private TestEnchantment(final int id, final String name) {
- super(id);
- this.name = name;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int getMaxLevel() {
- return 5;
- }
-
- @Override
- public int getStartLevel() {
- return 1;
- }
-
- @Override
- public EnchantmentTarget getItemTarget() {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public boolean canEnchantItem(ItemStack item) {
- return true;
- }
-
- @Override
- public boolean conflictsWith(Enchantment other) {
- return false;
- }
-
-}
diff --git a/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java b/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java index 7c4d7653..aa83af32 100644 --- a/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java +++ b/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java @@ -1,19 +1,9 @@ package org.bukkit.configuration.file; -import java.util.ArrayList; -import java.util.List; - -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; - import org.junit.Test; import static org.junit.Assert.*; public class YamlConfigurationTest extends FileConfigurationTest { - static { - TestEnchantment.registerEnchantments(); - } @Override public YamlConfiguration getConfig() { @@ -63,47 +53,4 @@ public class YamlConfigurationTest extends FileConfigurationTest { assertEquals(expected, result); } - - @Test - public void testSaveRestoreCompositeList() throws InvalidConfigurationException { - YamlConfiguration out = getConfig(); - - List<ItemStack> stacks = new ArrayList<ItemStack>(); - stacks.add(new ItemStack(1)); - stacks.add(new ItemStack(2)); - stacks.add(new ItemStack(3)); - stacks.add(new ItemStack(4, 17)); - stacks.add(new ItemStack(5, 63)); - stacks.add(new ItemStack(6, 1, (short) 1)); - stacks.add(new ItemStack(18, 32, (short) 2)); - - ItemStack item7 = new ItemStack(256); - item7.addEnchantment(Enchantment.getById(1), 1); - stacks.add(item7); - - ItemStack item8 = new ItemStack(257); - item8.addEnchantment(Enchantment.getById(2), 2); - item8.addEnchantment(Enchantment.getById(3), 1); - item8.addEnchantment(Enchantment.getById(4), 5); - item8.addEnchantment(Enchantment.getById(5), 4); - stacks.add(item8); - - out.set("composite-list.abc.def", stacks); - String yaml = out.saveToString(); - - YamlConfiguration in = new YamlConfiguration(); - in.loadFromString(yaml); - List<?> raw = in.getList("composite-list.abc.def"); - - assertEquals(stacks.size(), raw.size()); - assertEquals(stacks.get(0), raw.get(0)); - assertEquals(stacks.get(1), raw.get(1)); - assertEquals(stacks.get(2), raw.get(2)); - assertEquals(stacks.get(3), raw.get(3)); - assertEquals(stacks.get(4), raw.get(4)); - assertEquals(stacks.get(5), raw.get(5)); - assertEquals(stacks.get(6), raw.get(6)); - assertEquals(stacks.get(7), raw.get(7)); - assertEquals(stacks.get(8), raw.get(8)); - } } |