--- ../src-base/minecraft/org/bukkit/Material.java +++ ../src-work/minecraft/org/bukkit/Material.java @@ -3,6 +3,22 @@ import java.lang.reflect.Constructor; import java.util.Map; +// Cauldron start +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bukkit.inventory.ItemStack; + +import net.minecraftforge.common.util.EnumHelper; +import net.minecraftforge.cauldron.api.inventory.BukkitOreDictionary; +import net.minecraftforge.cauldron.api.inventory.OreDictionaryEntry; +// Cauldron end + import org.apache.commons.lang.Validate; import org.bukkit.map.MapView; import org.bukkit.material.Bed; @@ -418,14 +434,30 @@ private final int id; private final Constructor ctor; private static Material[] byId = new Material[383]; - private final static Map BY_NAME = Maps.newHashMap(); + private static Map BY_NAME = Maps.newHashMap(); // Cauldron - remove final private final int maxStack; private final short durability; + // Cauldron start + private static Object reflectionFactory; + private static Method newConstructorAccessor; + private static Method newInstance; + private static Method newFieldAccessor; + private static Method fieldAccessorSet; + private static boolean isSetup; + private boolean isForgeBlock = false; + // Cauldron end private Material(final int id) { this(id, 64); } + // Cauldron start - constructor used to set if the Material is a block or not + private Material(final int id, boolean flag) { + this(id, 64); + this.isForgeBlock = flag; + } + // Cauldron end + private Material(final int id, final int stack) { this(id, stack, MaterialData.class); } @@ -526,7 +558,7 @@ * @return true if this material is a block */ public boolean isBlock() { - return id < 256; + return id < 256 || isForgeBlock; // Cauldron } /** @@ -615,16 +647,202 @@ } catch (NumberFormatException ex) {} if (result == null) { - String filtered = name.toUpperCase(); - - filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", ""); + // Cauldron start - extract to normalizeName() + String filtered = normalizeName(name); result = BY_NAME.get(filtered); + // Cauldron end } + // Cauldron start - Try the ore dictionary + if (result == null) { + BukkitOreDictionary dict = net.minecraftforge.cauldron.api.Cauldron.getOreDictionary(); + OreDictionaryEntry entry = dict.getOreEntry(name); + if (entry != null) { + List items = dict.getDefinitions(entry); + if (items.size() > 0) { + // TODO check sanity on multiple item results + ItemStack item = items.get(0); + if (item.getDurability() == 0 || item.getDurability() == Short.MAX_VALUE) { + result = item.getType(); + } else { + // bad! we have an item with data! + } + } + } + } + // Cauldron end + return result; } + /* =============================== Cauldron START ============================= */ + + // use a normalize() function to ensure it is accessible after a round-trip + public static String normalizeName(String name) { + return name.toUpperCase().replaceAll("(:|\\s)", "_").replaceAll("\\W", ""); + } + + public static Material addMaterial(int id, boolean isBlock) + { + return addMaterial(id, "X" + String.valueOf(id), isBlock); + } + + public static Material addMaterial(int id, String name, boolean isBlock) { + if (byId[id] == null) { + String materialName = normalizeName(name); + Material material = (Material) EnumHelper.addEnum(Material.class, materialName, new Class[]{Integer.TYPE, Boolean.TYPE}, new Object[]{Integer.valueOf(id), isBlock}); + byId[id] = material; + BY_NAME.put(materialName, material); + BY_NAME.put("X" + String.valueOf(id), material); + return material; + } + return null; + } + + public static void setMaterialName(int id, String name, boolean flag) { + String materialName = normalizeName(name); + + if (byId[id] == null) + { + addMaterial(id, materialName, flag); + } + else // replace existing enum + { + /* TODO: find out how to do this with Forge's EnumHelper (addEnum?) - used for enabling descriptive (vs numeric) Material names + Material material = getMaterial(id); + BY_NAME.remove(material); + Material newMaterial = EnumHelper.replaceEnum(Material.class, material_name, material.ordinal(), new Class[] { Integer.TYPE }, new Object[] { Integer.valueOf(id) }); + if (newMaterial == null) + System.out.println("Error replacing Material " + name + " with id " + id); + else { + byId[id] = newMaterial; + BY_NAME.put(material_name, newMaterial); + } + */ + } + } + + private static void setup() + { + if (isSetup) + { + return; + } + try { + Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory", new Class[0]); + reflectionFactory = getReflectionFactory.invoke(null, new Object[0]); + newConstructorAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newConstructorAccessor", new Class[] { Constructor.class }); + newInstance = Class.forName("sun.reflect.ConstructorAccessor").getDeclaredMethod("newInstance", new Class[] { Object[].class }); + newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", new Class[] { Field.class, Boolean.TYPE }); + fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", new Class[] { Object.class, Object.class }); + } catch (Exception e) { + e.printStackTrace(); + } + + isSetup = true; + } + + private static Object getConstructorAccessor(Class enumClass, Class[] additionalParameterTypes) throws Exception { + Class[] parameterTypes = null; + + parameterTypes = new Class[additionalParameterTypes.length + 2]; + parameterTypes[0] = String.class; + parameterTypes[1] = Integer.TYPE; + System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length); + + return newConstructorAccessor.invoke(reflectionFactory, new Object[] { enumClass.getDeclaredConstructor(parameterTypes) }); + } + + private static > T makeEnum(Class enumClass, String value, int ordinal, Class[] additionalTypes, Object[] additionalValues) throws Exception { + Object[] parms = null; + + parms = new Object[additionalValues.length + 2]; + parms[0] = value; + parms[1] = Integer.valueOf(ordinal); + System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length); + + return (T)enumClass.cast(newInstance.invoke(getConstructorAccessor(enumClass, additionalTypes), new Object[] { parms })); + } + + private static void setFailsafeFieldValue(Field field, Object target, Object value) throws Exception { + field.setAccessible(true); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & 0xFFFFFFEF); + Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, new Object[] { field, Boolean.valueOf(false) }); + fieldAccessorSet.invoke(fieldAccessor, new Object[] { target, value }); + } + + private static void blankField(Class enumClass, String fieldName) throws Exception { + for (Field field : Class.class.getDeclaredFields()) + if (field.getName().contains(fieldName)) { + field.setAccessible(true); + setFailsafeFieldValue(field, enumClass, null); + break; + } + } + + private static void cleanEnumCache(Class enumClass) throws Exception + { + blankField(enumClass, "enumConstantDirectory"); + blankField(enumClass, "enumConstants"); + } + + public static > T replaceEnum(Class enumType, String enumName, int ordinal, Class[] paramTypes, Object[] paramValues) + { + if (!isSetup) setup(); + Field valuesField = null; + Field[] fields = enumType.getDeclaredFields(); + int flags = 4122; + String valueType = String.format("[L%s;", new Object[] { enumType.getName() }); + + for (Field field : fields) { + if (((field.getModifiers() & flags) != flags) || (!field.getType().getName().equals(valueType))) { + continue; + } + valuesField = field; + break; + } + + valuesField.setAccessible(true); + try + { + Enum[] previousValues = (Enum[])(Enum[])valuesField.get(enumType); + Enum[] newValues = new Enum[previousValues.length]; + Enum newValue = null; + for (Enum enumValue : previousValues) + { + if (enumValue.ordinal() == ordinal) + { + newValue = makeEnum(enumType, enumName, ordinal, paramTypes, paramValues); + newValues[enumValue.ordinal()] = newValue; + } + else newValues[enumValue.ordinal()] = enumValue; + } + List values = new ArrayList(Arrays.asList(newValues)); + + setFailsafeFieldValue(valuesField, null, values.toArray((Enum[])(Enum[])Array.newInstance(enumType, 0))); + cleanEnumCache(enumType); + return (T) newValue; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + } + /* =============================== Cauldron END============================= */ + static { + // Cauldron start + byId = new Material[32000]; + BY_NAME = Maps.newHashMap(); + + reflectionFactory = null; + newConstructorAccessor = null; + newInstance = null; + newFieldAccessor = null; + fieldAccessorSet = null; + isSetup = false; + // Cauldron end for (Material material : values()) { if (byId.length > material.id) { byId[material.id] = material;