diff --git a/src/main/java/cpw/mods/fml/common/Loader.java b/src/main/java/cpw/mods/fml/common/Loader.java index 7d9f562..820d513 100644 --- a/src/main/java/cpw/mods/fml/common/Loader.java +++ b/src/main/java/cpw/mods/fml/common/Loader.java @@ -75,6 +75,7 @@ import cpw.mods.fml.common.functions.ModIdFunction; import cpw.mods.fml.common.registry.GameData; import cpw.mods.fml.common.registry.GameRegistry.Type; +import cpw.mods.fml.common.registry.ItemStackHolderInjector; import cpw.mods.fml.common.registry.ObjectHolderRegistry; import cpw.mods.fml.common.toposort.ModSorter; import cpw.mods.fml.common.toposort.ModSortingException; @@ -530,8 +531,10 @@ return; } ObjectHolderRegistry.INSTANCE.findObjectHolders(discoverer.getASMTable()); + ItemStackHolderInjector.INSTANCE.findHolders(discoverer.getASMTable()); modController.distributeStateMessage(LoaderState.PREINITIALIZATION, discoverer.getASMTable(), canonicalConfigDir); ObjectHolderRegistry.INSTANCE.applyObjectHolders(); + ItemStackHolderInjector.INSTANCE.inject(); modController.transition(LoaderState.INITIALIZATION, false); progressBar.step("Initializing Minecraft Engine"); } @@ -714,6 +717,7 @@ progressBar.step("Initializing mods Phase 3"); modController.transition(LoaderState.POSTINITIALIZATION, false); modController.distributeStateMessage(FMLInterModComms.IMCEvent.class); + ItemStackHolderInjector.INSTANCE.inject(); modController.distributeStateMessage(LoaderState.POSTINITIALIZATION); progressBar.step("Finishing up"); modController.transition(LoaderState.AVAILABLE, false); diff --git a/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java b/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java index 20dc885..15da478 100644 --- a/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java +++ b/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java @@ -32,6 +32,10 @@ import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.FurnaceRecipes; import net.minecraft.item.crafting.IRecipe; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTException; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import net.minecraft.world.chunk.IChunkProvider; @@ -39,6 +43,8 @@ import org.apache.logging.log4j.Level; import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -476,4 +482,70 @@ */ String value(); } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface ItemStackHolder + { + /** + * The registry name of the item being looked up. + * @return The registry name + */ + public String value(); + + /** + * The metadata or damage value for the itemstack, defaults to 0. + * @return the metadata value + */ + public int meta() default 0; + + /** + * The string serialized nbt value for the itemstack. Defaults to empty for no nbt. + * + * @return a nbt string + */ + public String nbt() default ""; + } + + /** + * Makes an {@link ItemStack} based on the itemName reference, with supplied meta, stackSize and nbt, if possible + * + * Will return null if the item doesn't exist (because it's not from a loaded mod for example) + * Will throw a {@link RuntimeException} if the nbtString is invalid for use in an {@link ItemStack} + * + * @param itemName a registry name reference + * @param meta the meta + * @param stackSize the stack size + * @param nbtString an nbt stack as a string, will be processed by {@link JsonToNBT} + * @return a new itemstack + */ + public static ItemStack makeItemStack(String itemName, int meta, int stackSize, String nbtString) + { + if (itemName == null) throw new IllegalArgumentException("The itemName cannot be null"); + Item item = GameData.getItemRegistry().getObject(itemName); + if (item == null) { + FMLLog.getLogger().log(Level.TRACE, "Unable to find item with name {}", itemName); + return null; + } + ItemStack is = new ItemStack(item,1,meta); + if (!Strings.isNullOrEmpty(nbtString)) { + NBTBase nbttag = null; + try + { + nbttag = JsonToNBT.func_150315_a(nbtString); + } catch (NBTException e) + { + FMLLog.getLogger().log(Level.WARN, "Encountered an exception parsing ItemStack NBT string {}", nbtString, e); + throw Throwables.propagate(e); + } + if (!(nbttag instanceof NBTTagCompound)) { + FMLLog.getLogger().log(Level.WARN, "Unexpected NBT string - multiple values {}", nbtString); + throw new RuntimeException("Invalid NBT JSON"); + } else { + is.setTagCompound((NBTTagCompound) nbttag); + } + } + return is; + } + } \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderInjector.java b/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderInjector.java new file mode 100644 index 0000000..76d0e0d --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderInjector.java @@ -0,0 +1,80 @@ +package cpw.mods.fml.common.registry; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; + +public enum ItemStackHolderInjector +{ + INSTANCE; + + private List itemStackHolders = Lists.newArrayList(); + + public void inject() { + FMLLog.getLogger().log(Level.INFO, "Injecting itemstacks"); + for (ItemStackHolderRef ishr: itemStackHolders) { + ishr.apply(); + } + FMLLog.getLogger().log(Level.INFO, "Itemstack injection complete"); + } + + public void findHolders(ASMDataTable table) { + FMLLog.info("Identifying ItemStackHolder annotations"); + Set allItemStackHolders = table.getAll(GameRegistry.ItemStackHolder.class.getName()); + Map> classCache = Maps.newHashMap(); + for (ASMData data : allItemStackHolders) + { + String className = data.getClassName(); + String annotationTarget = data.getObjectName(); + String value = (String) data.getAnnotationInfo().get("value"); + int meta = data.getAnnotationInfo().containsKey("meta") ? (Integer) data.getAnnotationInfo().get("meta") : 0; + String nbt = data.getAnnotationInfo().containsKey("nbt") ? (String) data.getAnnotationInfo().get("nbt") : ""; + addHolder(classCache, className, annotationTarget, value, meta, nbt); + } + FMLLog.info("Found %d ItemStackHolder annotations", allItemStackHolders.size()); + + } + + private void addHolder(Map> classCache, String className, String annotationTarget, String value, Integer meta, String nbt) + { + Class clazz; + if (classCache.containsKey(className)) + { + clazz = classCache.get(className); + } + else + { + try + { + clazz = Class.forName(className, true, getClass().getClassLoader()); + classCache.put(className, clazz); + } + catch (Exception ex) + { + // unpossible? + throw Throwables.propagate(ex); + } + } + try + { + Field f = clazz.getField(annotationTarget); + itemStackHolders.add(new ItemStackHolderRef(f, value, meta, nbt)); + } + catch (Exception ex) + { + // unpossible? + throw Throwables.propagate(ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderRef.java b/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderRef.java new file mode 100644 index 0000000..4d0014c --- /dev/null +++ b/src/main/java/cpw/mods/fml/common/registry/ItemStackHolderRef.java @@ -0,0 +1,84 @@ +package cpw.mods.fml.common.registry; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import net.minecraft.item.ItemStack; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Throwables; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.registry.GameRegistry.ItemStackHolder; + + +/** + * Internal class used in tracking {@link ItemStackHolder} references + * + * @author cpw + * + */ +class ItemStackHolderRef { + private Field field; + private String itemName; + private int meta; + private String serializednbt; + + + ItemStackHolderRef(Field field, String itemName, int meta, String serializednbt) + { + this.field = field; + this.itemName = itemName; + this.meta = meta; + this.serializednbt = serializednbt; + makeWritable(field); + } + + private static Field modifiersField; + private static Object reflectionFactory; + private static Method newFieldAccessor; + private static Method fieldAccessorSet; + private static void makeWritable(Field f) + { + try + { + if (modifiersField == null) + { + Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); + reflectionFactory = getReflectionFactory.invoke(null); + newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); + fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); + modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + } + modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); + } catch (Exception e) + { + throw Throwables.propagate(e); + } + } + + public void apply() + { + ItemStack is; + try + { + is = GameRegistry.makeItemStack(itemName, meta, 1, serializednbt); + } catch (RuntimeException e) + { + FMLLog.getLogger().log(Level.ERROR, "Caught exception processing itemstack {},{},{} in annotation at {}.{}", itemName, meta, serializednbt,field.getClass().getName(),field.getName()); + throw e; + } + try + { + Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); + fieldAccessorSet.invoke(fieldAccessor, null, is); + } + catch (Exception e) + { + FMLLog.getLogger().log(Level.WARN, "Unable to set {} with value {},{},{}", this.field, this.itemName, this.meta, this.serializednbt); + } + } +} \ No newline at end of file