diff --git a/fml/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/fml/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index 15159208fa..878de2e200 100644 --- a/fml/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/fml/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -66,6 +66,7 @@ import net.minecraft.network.status.INetHandlerStatusServer; import net.minecraft.server.MinecraftServer; import net.minecraft.util.IThreadListener; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StringUtils; import net.minecraft.world.WorldSettings; import net.minecraft.world.storage.SaveFormatOld; import net.minecraftforge.fml.client.registry.RenderingRegistry; @@ -991,9 +992,17 @@ public class FMLClientHandler implements IFMLSidedHandler public void processWindowMessages() { // workaround for windows requiring messages being processed on the main thread - if(LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_WINDOWS) - { - Display.processMessages(); - } + if (LWJGLUtil.getPlatform() != LWJGLUtil.PLATFORM_WINDOWS) return; + // If we can't grab the mutex, the update call is blocked, probably in native code, just skip it and carry on + // We'll get another go next time + if (!SplashProgress.mutex.tryAcquire()) return; + Display.processMessages(); + SplashProgress.mutex.release(); + } + + @Override + public String stripSpecialChars(String message) + { + return StringUtils.stripControlCodes(message); } } diff --git a/fml/src/main/java/net/minecraftforge/fml/client/SplashProgress.java b/fml/src/main/java/net/minecraftforge/fml/client/SplashProgress.java index 5370c230dc..30c20654af 100644 --- a/fml/src/main/java/net/minecraftforge/fml/client/SplashProgress.java +++ b/fml/src/main/java/net/minecraftforge/fml/client/SplashProgress.java @@ -63,6 +63,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.nio.IntBuffer; import java.util.Iterator; import java.util.Properties; +import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -131,6 +132,7 @@ public class SplashProgress private static int barBorderColor; private static int barColor; private static int barBackgroundColor; + static final Semaphore mutex = new Semaphore(1); private static String getString(String name, String def) { @@ -352,7 +354,16 @@ public class SplashProgress glEnd(); glDisable(GL_TEXTURE_2D); + // We use mutex to indicate safely to the main thread that we're taking the display global lock + // So the main thread can skip processing messages while we're updating. + // There are system setups where this call can pause for a while, because the GL implementation + // is trying to impose a framerate or other thing is occurring. Without the mutex, the main + // thread would delay waiting for the same global display lock + mutex.acquireUninterruptibly(); Display.update(); + // As soon as we're done, we release the mutex. The other thread can now ping the processmessages + // call as often as it wants until we get get back here again + mutex.release(); if(pause) { clearGL(); diff --git a/fml/src/main/java/net/minecraftforge/fml/common/FMLCommonHandler.java b/fml/src/main/java/net/minecraftforge/fml/common/FMLCommonHandler.java index eabfb0b7a2..d8ee59c933 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/FMLCommonHandler.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/FMLCommonHandler.java @@ -764,4 +764,8 @@ public class FMLCommonHandler props.clear(); return null; } + public String stripSpecialChars(String message) + { + return sidedDelegate != null ? sidedDelegate.stripSpecialChars(message) : message; + } } diff --git a/fml/src/main/java/net/minecraftforge/fml/common/IFMLSidedHandler.java b/fml/src/main/java/net/minecraftforge/fml/common/IFMLSidedHandler.java index cbf851a3c3..37cc402f13 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/IFMLSidedHandler.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/IFMLSidedHandler.java @@ -64,4 +64,6 @@ public interface IFMLSidedHandler IThreadListener getWorldThread(INetHandler net); void processWindowMessages(); + + String stripSpecialChars(String message); } diff --git a/fml/src/main/java/net/minecraftforge/fml/common/Loader.java b/fml/src/main/java/net/minecraftforge/fml/common/Loader.java index b2fea151f5..ad3aa4e15c 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -39,6 +39,7 @@ import net.minecraftforge.fml.common.functions.ArtifactVersionNameFunction; import net.minecraftforge.fml.common.functions.ModIdFunction; import net.minecraftforge.fml.common.registry.GameData; import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.common.registry.ItemStackHolderInjector; import net.minecraftforge.fml.common.registry.ObjectHolderRegistry; import net.minecraftforge.fml.common.registry.GameRegistry.Type; import net.minecraftforge.fml.common.toposort.ModSorter; @@ -524,8 +525,10 @@ public class Loader 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"); } @@ -614,7 +617,7 @@ public class Loader public String getFMLVersionString() { - return String.format("%s.%s.%s.%s", major, minor, rev, build); + return "8.0.99.99"; } public ClassLoader getModClassLoader() @@ -708,6 +711,7 @@ public class Loader 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/fml/src/main/java/net/minecraftforge/fml/common/ProgressManager.java b/fml/src/main/java/net/minecraftforge/fml/common/ProgressManager.java index cd4716b823..e66b5c6d73 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/ProgressManager.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/ProgressManager.java @@ -71,7 +71,7 @@ public class ProgressManager { if(step >= steps) throw new IllegalStateException("too much steps for ProgressBar " + title); step++; - this.message = message; + this.message = FMLCommonHandler.instance().stripSpecialChars(message); FMLCommonHandler.instance().processWindowMessages(); } diff --git a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/EventSubscriptionTransformer.java b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/EventSubscriptionTransformer.java index 6f2c1172b8..46842f005f 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/EventSubscriptionTransformer.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/EventSubscriptionTransformer.java @@ -65,7 +65,7 @@ public class EventSubscriptionTransformer implements IClassTransformer { if (buildEvents(classNode)) { - ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); classNode.accept(cw); return cw.toByteArray(); } diff --git a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/MarkerTransformer.java b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/MarkerTransformer.java index 1fcfcb1aea..1ee32d23ce 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/MarkerTransformer.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/MarkerTransformer.java @@ -100,7 +100,7 @@ public class MarkerTransformer implements IClassTransformer @Override public byte[] transform(String name, String transformedName, byte[] bytes) { - if (bytes == null) { return null; } + if (bytes == null) { return null; } if (!markers.containsKey(name)) { return bytes; } ClassNode classNode = new ClassNode(); diff --git a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/SideTransformer.java b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/SideTransformer.java index 36235f3367..db1ae089c6 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/SideTransformer.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/asm/transformers/SideTransformer.java @@ -34,7 +34,7 @@ public class SideTransformer implements IClassTransformer @Override public byte[] transform(String name, String transformedName, byte[] bytes) { - if (bytes == null) { return null; } + if (bytes == null) { return null; } ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(bytes); diff --git a/fml/src/main/java/net/minecraftforge/fml/common/registry/GameRegistry.java b/fml/src/main/java/net/minecraftforge/fml/common/registry/GameRegistry.java index c6c54e1c06..352dd7990e 100644 --- a/fml/src/main/java/net/minecraftforge/fml/common/registry/GameRegistry.java +++ b/fml/src/main/java/net/minecraftforge/fml/common/registry/GameRegistry.java @@ -32,6 +32,10 @@ import net.minecraft.item.ItemStack; 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.util.ResourceLocation; import net.minecraft.world.World; @@ -47,6 +51,8 @@ import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 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; @@ -451,4 +457,70 @@ public class GameRegistry */ 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_180713_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; + } + } diff --git a/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderInjector.java b/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderInjector.java new file mode 100644 index 0000000000..9b369785fd --- /dev/null +++ b/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderInjector.java @@ -0,0 +1,81 @@ +package net.minecraftforge.fml.common.registry; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.discovery.ASMDataTable; +import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + + +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); + } + } +} diff --git a/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderRef.java b/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderRef.java new file mode 100644 index 0000000000..2b941ecc8a --- /dev/null +++ b/fml/src/main/java/net/minecraftforge/fml/common/registry/ItemStackHolderRef.java @@ -0,0 +1,83 @@ +package net.minecraftforge.fml.common.registry; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.FMLLog; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Throwables; + + + +/** + * 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); + } + } +} diff --git a/fml/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java b/fml/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java index 32ff2c3c2d..be2f2d67a6 100644 --- a/fml/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java +++ b/fml/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java @@ -272,4 +272,10 @@ public class FMLServerHandler implements IFMLSidedHandler { // NOOP } + + @Override + public String stripSpecialChars(String message) + { + return message; + } } diff --git a/patches/minecraft/net/minecraft/client/gui/FontRenderer.java.patch b/patches/minecraft/net/minecraft/client/gui/FontRenderer.java.patch index 82a0529fa8..cf001e1ac1 100644 --- a/patches/minecraft/net/minecraft/client/gui/FontRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/FontRenderer.java.patch @@ -17,7 +17,7 @@ boolean flag1 = (c0 == 0 || j == -1 || this.field_78293_l) && p_78255_2_; if (flag1) -@@ -583,11 +584,6 @@ +@@ -592,11 +593,6 @@ int j = this.field_78287_e[p_78263_1_] >>> 4; int k = this.field_78287_e[p_78263_1_] & 15; diff --git a/src/main/java/net/minecraftforge/classloading/FMLForgePlugin.java b/src/main/java/net/minecraftforge/classloading/FMLForgePlugin.java index 2a66eb4356..5cb3f3f307 100644 --- a/src/main/java/net/minecraftforge/classloading/FMLForgePlugin.java +++ b/src/main/java/net/minecraftforge/classloading/FMLForgePlugin.java @@ -13,7 +13,7 @@ public class FMLForgePlugin implements IFMLLoadingPlugin @Override public String[] getASMTransformerClass() { - return new String[0]; + return new String[] { "net.minecraftforge.classloading.FluidIdTransformer" }; } @Override diff --git a/src/main/java/net/minecraftforge/classloading/FluidIdTransformer.java b/src/main/java/net/minecraftforge/classloading/FluidIdTransformer.java new file mode 100644 index 0000000000..bd6a49a87f --- /dev/null +++ b/src/main/java/net/minecraftforge/classloading/FluidIdTransformer.java @@ -0,0 +1,55 @@ +package net.minecraftforge.classloading; + +import java.util.ListIterator; + +import net.minecraft.launchwrapper.IClassTransformer; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import net.minecraftforge.fml.common.FMLLog;; + +public class FluidIdTransformer implements IClassTransformer { + private static final String FLUID_TYPE = "net/minecraftforge/fluids/FluidStack"; + private static final String GETID_NAME = "getFluidID"; + private static final String LEGACY_FIELDNAME = "fluidID"; + private static final String GETID_DESC = "()I"; + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (basicClass == null) + return null; + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + for (MethodNode m: classNode.methods) + { + for (ListIterator it = m.instructions.iterator(); it.hasNext(); ) + { + AbstractInsnNode insnNode = it.next(); + if (insnNode.getType() == AbstractInsnNode.FIELD_INSN) + { + FieldInsnNode fi = (FieldInsnNode)insnNode; + if (FLUID_TYPE.equals(fi.owner) && LEGACY_FIELDNAME.equals(fi.name) && fi.getOpcode() == Opcodes.GETFIELD) + { + FMLLog.fine("Method %s.%s%s: Replacing GETFIELD fluidID with INVOKEVIRTUAL getFluidID", name, m.name, m.desc); + it.remove(); + MethodInsnNode replace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, FLUID_TYPE, GETID_NAME, GETID_DESC, false); + it.add(replace); + } + } + } + } + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + +} diff --git a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java b/src/main/java/net/minecraftforge/common/ForgeChunkManager.java index 4fe166f877..2ceb506d46 100644 --- a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java +++ b/src/main/java/net/minecraftforge/common/ForgeChunkManager.java @@ -663,7 +663,7 @@ public class ForgeChunkManager throw new RuntimeException("Invalid ticket request"); } - int allowedCount = ticketConstraints.containsKey(modId) ? ticketConstraints.get(modId) : defaultMaxCount; + int allowedCount = getMaxTicketLengthFor(modId); if (tickets.get(world).get(modId).size() >= allowedCount) { diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 6fcc2e9cc5..4db059e265 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -26,6 +26,7 @@ import net.minecraftforge.classloading.FMLForgePlugin; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; import net.minecraftforge.common.network.ForgeNetworkHandler; +import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.oredict.OreDictionary; import net.minecraftforge.oredict.RecipeSorter; import net.minecraftforge.server.command.ForgeCommand; @@ -60,7 +61,6 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC public static boolean removeErroringEntities = false; public static boolean removeErroringTileEntities = false; public static boolean disableStitchedFileSaving = false; - public static boolean forceDuplicateFluidBlockCrash = true; public static boolean fullBoundingBoxLadders = false; public static double zombieSummonBaseChance = 0.1; public static int[] blendRanges = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34 }; @@ -68,6 +68,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC public static boolean shouldSortRecipies = true; public static boolean disableVersionCheck = false; public static int defaultSpawnFuzz = 20; + public static boolean defaultHasSpawnFuzz = true; private static Configuration config; @@ -157,17 +158,6 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC shouldSortRecipies = prop.getBoolean(shouldSortRecipies); propOrder.add(prop.getName()); - prop = config.get(Configuration.CATEGORY_GENERAL, "forceDuplicateFluidBlockCrash", true); - prop.comment = "Set this to true to force a crash if more than one block attempts to link back to the same Fluid. Enabled by default."; - prop.setLanguageKey("forge.configgui.forceDuplicateFluidBlockCrash").setRequiresMcRestart(true); - forceDuplicateFluidBlockCrash = prop.getBoolean(true); - propOrder.add(prop.getName()); - - if (!forceDuplicateFluidBlockCrash) - { - FMLLog.warning("Disabling forced crashes on duplicate Fluid Blocks - USE AT YOUR OWN RISK"); - } - prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringEntities", false); prop.comment = "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."; prop.setLanguageKey("forge.configgui.removeErroringEntities").setRequiresWorldRestart(true); @@ -225,6 +215,12 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC defaultSpawnFuzz = prop.getInt(20); propOrder.add(prop.getName()); + prop = config.get(Configuration.CATEGORY_GENERAL, "spawnHasFuzz", Boolean.TRUE, + "If the overworld has ANY spawn fuzz at all. If not, the spawn will always be the exact same location."); + prop.setLanguageKey("forge.configgui.hasspawnfuzz").setRequiresWorldRestart(false); + defaultHasSpawnFuzz = prop.getBoolean(Boolean.TRUE); + propOrder.add(prop.getName()); + config.setCategoryPropertyOrder(CATEGORY_GENERAL, propOrder); if (config.hasChanged()) @@ -280,7 +276,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC MinecraftForge.EVENT_BUS.register(MinecraftForge.INTERNAL_HANDLER); ForgeChunkManager.captureConfig(evt.getModConfigurationDirectory()); FMLCommonHandler.instance().bus().register(this); - + if (!ForgeModContainer.disableVersionCheck) { ForgeVersion.startVersionCheck(); @@ -301,6 +297,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC { RecipeSorter.sortCraftManager(); } + FluidRegistry.validateFluidRegistry(); } @Subscribe @@ -314,6 +311,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC NBTTagCompound forgeData = new NBTTagCompound(); NBTTagCompound dimData = DimensionManager.saveDimensionDataMap(); forgeData.setTag("DimensionData", dimData); + FluidRegistry.writeDefaultFluidList(forgeData); return forgeData; } @@ -321,6 +319,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC public void readData(SaveHandler handler, WorldInfo info, Map propertyMap, NBTTagCompound tag) { DimensionManager.loadDimensionDataMap(tag.hasKey("DimensionData") ? tag.getCompoundTag("DimensionData") : null); + FluidRegistry.loadFluidDefaults(tag); } @Subscribe diff --git a/src/main/java/net/minecraftforge/common/network/FluidIdRegistryMessageHandler.java b/src/main/java/net/minecraftforge/common/network/FluidIdRegistryMessageHandler.java index 975462a305..59e233710c 100644 --- a/src/main/java/net/minecraftforge/common/network/FluidIdRegistryMessageHandler.java +++ b/src/main/java/net/minecraftforge/common/network/FluidIdRegistryMessageHandler.java @@ -12,7 +12,7 @@ public class FluidIdRegistryMessageHandler extends SimpleChannelInboundHandler fluidIds = HashBiMap.create(); + BiMap fluidIds = HashBiMap.create(); + Set defaultFluids = Sets.newHashSet(); @Override void toBytes(ByteBuf bytes) { - Map ids = FluidRegistry.getRegisteredFluidIDs(); + Map ids = FluidRegistry.getRegisteredFluidIDsByFluid(); bytes.writeInt(ids.size()); - for (Map.Entry entry : ids.entrySet()) + for (Map.Entry entry : ids.entrySet()) { - ByteBufUtils.writeUTF8String(bytes,entry.getKey()); + ByteBufUtils.writeUTF8String(bytes,entry.getKey().getName()); bytes.writeInt(entry.getValue()); } + for (Map.Entry entry : ids.entrySet()) + { + String defaultName = FluidRegistry.getDefaultFluidName(entry.getKey()); + ByteBufUtils.writeUTF8String(bytes, defaultName); + } } @Override @@ -60,7 +72,21 @@ public abstract class ForgeMessage { for (int i = 0; i < listSize; i++) { String fluidName = ByteBufUtils.readUTF8String(bytes); int fluidId = bytes.readInt(); - fluidIds.put(fluidName, fluidId); + fluidIds.put(FluidRegistry.getFluid(fluidName), fluidId); + } + // do we have a defaults list? + + if (bytes.isReadable()) + { + for (int i = 0; i < listSize; i++) + { + defaultFluids.add(ByteBufUtils.readUTF8String(bytes)); + } + } + else + { + FMLLog.getLogger().log(Level.INFO, "Legacy server message contains no default fluid list - there may be problems with fluids"); + defaultFluids.clear(); } } } diff --git a/src/main/java/net/minecraftforge/fluids/BlockFluidBase.java b/src/main/java/net/minecraftforge/fluids/BlockFluidBase.java index 9a1a71bdd5..a4d3fe2afd 100644 --- a/src/main/java/net/minecraftforge/fluids/BlockFluidBase.java +++ b/src/main/java/net/minecraftforge/fluids/BlockFluidBase.java @@ -96,6 +96,13 @@ public abstract class BlockFluidBase extends Block implements IFluidBlock protected final String fluidName; + /** + * This is the fluid used in the constructor. Use this reference to configure things + * like icons for your block. It might not be active in the registry, so do + * NOT expose it. + */ + protected final Fluid definedFluid; + public BlockFluidBase(Fluid fluid, Material material) { super(material); @@ -111,6 +118,7 @@ public abstract class BlockFluidBase extends Block implements IFluidBlock this.densityDir = fluid.density > 0 ? -1 : 1; fluid.setBlock(this); + this.definedFluid = fluid; displacements.putAll(defaultDisplacements); this.setDefaultState(blockState.getBaseState().withProperty(LEVEL, 0)); } diff --git a/src/main/java/net/minecraftforge/fluids/Fluid.java b/src/main/java/net/minecraftforge/fluids/Fluid.java index 537e5ddb5f..d827238005 100644 --- a/src/main/java/net/minecraftforge/fluids/Fluid.java +++ b/src/main/java/net/minecraftforge/fluids/Fluid.java @@ -66,13 +66,17 @@ public class Fluid * * Default value is approximately the real-life room temperature of water in degrees Kelvin. */ - protected int temperature = 295; + protected int temperature = 300; /** * Viscosity ("thickness") of the fluid - completely arbitrary; negative values are not * permissible. * * Default value is approximately the real-life density of water in m/s^2 (x10^-3). + * + * Higher viscosity means that a fluid flows more slowly, like molasses. + * Lower viscosity means that a fluid flows more quickly, like helium. + * */ protected int viscosity = 1000; @@ -117,17 +121,10 @@ public class Fluid { this.block = block; } - else if (!ForgeModContainer.forceDuplicateFluidBlockCrash) - { - FMLLog.warning("A mod has attempted to assign Block " + block + " to the Fluid '" + fluidName + "' but this Fluid has already been linked to BlockID " - + this.block + ". Configure your mods to prevent this from happening."); - } else { - FMLLog.severe("A mod has attempted to assign BlockID " + block + " to the Fluid '" + fluidName + "' but this Fluid has already been linked to BlockID " - + this.block + ". Configure your mods to prevent this from happening."); - throw new LoaderException(new RuntimeException("A mod has attempted to assign BlockID " + block + " to the Fluid '" + fluidName - + "' but this Fluid has already been linked to BlockID " + this.block + ". Configure your mods to prevent this from happening.")); + FMLLog.warning("A mod has attempted to assign Block " + block + " to the Fluid '" + fluidName + "' but this Fluid has already been linked to the Block " + + this.block + ". You may have duplicate Fluid Blocks as a result. It *may* be possible to configure your mods to avoid this."); } return this; } @@ -320,19 +317,4 @@ public class Fluid public int getColor(World world, BlockPos pos){ return getColor(); } public TextureAtlasSprite getIcon(World world, BlockPos pos){ return getIcon(); } - private static Map legacyNames = Maps.newHashMap(); - static String convertLegacyName(String fluidName) - { - return fluidName != null && legacyNames.containsKey(fluidName) ? legacyNames.get(fluidName) : fluidName; - } - - /** - * Register a legacy liquid name with the Fluids system - * @param legacyName The legacy name to recognize - * @param canonicalName The canonical fluid name it will become - */ - public static void registerLegacyName(String legacyName, String canonicalName) - { - legacyNames.put(legacyName.toLowerCase(Locale.ENGLISH), canonicalName); - } } diff --git a/src/main/java/net/minecraftforge/fluids/FluidContainerRegistry.java b/src/main/java/net/minecraftforge/fluids/FluidContainerRegistry.java index 6e55d84215..435598c030 100644 --- a/src/main/java/net/minecraftforge/fluids/FluidContainerRegistry.java +++ b/src/main/java/net/minecraftforge/fluids/FluidContainerRegistry.java @@ -11,6 +11,7 @@ import java.util.Set; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.eventhandler.Event; import net.minecraft.init.Items; import net.minecraft.item.Item; @@ -20,16 +21,16 @@ import net.minecraftforge.common.MinecraftForge; /** * Register simple items that contain fluids here. Useful for buckets, bottles, and things that have * ID/metadata mappings. - * + * * For more complex items, use {@link IFluidContainerItem} instead. - * + * * @author King Lemming - * + * */ public abstract class FluidContainerRegistry { - // Holder object that implements HashCode for an ItemStack, - // the local maps are not guaranteed to have the same internal generic structure, + // Holder object that implements HashCode for an ItemStack, + // the local maps are not guaranteed to have the same internal generic structure, // but the external interface for checking ItemStacks will still exist. private static class ContainerKey { @@ -51,7 +52,7 @@ public abstract class FluidContainerRegistry code = 31*code + container.getItem().hashCode(); code = 31*code + container.getItemDamage(); if (fluid != null) - code = 31*code + fluid.fluidID; + code = 31*code + fluid.getFluid().hashCode(); return code; } @Override @@ -64,7 +65,7 @@ public abstract class FluidContainerRegistry if (fluid == null && ck.fluid != null) return false; if (fluid != null && ck.fluid == null) return false; if (fluid == null && ck.fluid == null) return true; - if (fluid.fluidID != ck.fluid.fluidID) return false; + if (fluid.getFluid() != ck.fluid.getFluid()) return false; return true; } } @@ -89,7 +90,7 @@ public abstract class FluidContainerRegistry /** * Register a new fluid containing item. - * + * * @param stack * FluidStack containing the type and amount of the fluid stored in the item. * @param filledContainer @@ -106,7 +107,7 @@ public abstract class FluidContainerRegistry /** * Register a new fluid containing item. The item is assumed to hold 1000 mB of fluid. Also * registers the Fluid if possible. - * + * * @param fluid * Fluid type that is stored in the item. * @param filledContainer @@ -126,12 +127,12 @@ public abstract class FluidContainerRegistry /** * Register a new fluid containing item that does not have an empty container. - * + * * @param stack * FluidStack containing the type and amount of the fluid stored in the item. * @param filledContainer * ItemStack representing the container when it is full. - * @return True if container was successfully registered; false if it already is. + * @return True if container was successfully registered; false if it already is, or an invalid parameter was passed. */ public static boolean registerFluidContainer(FluidStack stack, ItemStack filledContainer) { @@ -141,12 +142,12 @@ public abstract class FluidContainerRegistry /** * Register a new fluid containing item that does not have an empty container. The item is * assumed to hold 1000 mB of fluid. Also registers the Fluid if possible. - * + * * @param fluid * Fluid type that is stored in the item. * @param filledContainer * ItemStack representing the container when it is full. - * @return True if container was successfully registered; false if it already is. + * @return True if container was successfully registered; false if it already is, or an invalid parameter was passed. */ public static boolean registerFluidContainer(Fluid fluid, ItemStack filledContainer) { @@ -159,17 +160,22 @@ public abstract class FluidContainerRegistry /** * Register a new fluid containing item. - * + * * @param data * See {@link FluidContainerData}. - * @return True if container was successfully registered; false if it already is. + * @return True if container was successfully registered; false if it already is, or an invalid parameter was passed. */ public static boolean registerFluidContainer(FluidContainerData data) { - if (isFilledContainer(data.filledContainer)) + if (isFilledContainer(data.filledContainer) || data.filledContainer == null) { return false; } + if (data.fluid == null || data.fluid.getFluid() == null) + { + FMLLog.bigWarning("Invalid registration attempt for a fluid container item %s has occurred. The registration has been denied to prevent crashes. The mod responsible for the registration needs to correct this.", data.filledContainer.getItem().getUnlocalizedName(data.filledContainer)); + return false; + } containerFluidMap.put(new ContainerKey(data.filledContainer), data); if (data.emptyContainer != null && data.emptyContainer != NULL_EMPTYCONTAINER) @@ -184,7 +190,7 @@ public abstract class FluidContainerRegistry /** * Determines the fluid type and amount inside a container. - * + * * @param container * The fluid container. * @return FluidStack representing stored fluid. @@ -202,9 +208,9 @@ public abstract class FluidContainerRegistry /** * Attempts to fill an empty container with a fluid. - * + * * NOTE: Returns null on fail, NOT the empty container. - * + * * @param fluid * FluidStack containing the type and amount of fluid to fill. * @param container @@ -228,7 +234,7 @@ public abstract class FluidContainerRegistry /** * Attempts to empty a full container. - * + * * @param container * ItemStack representing the full container. * @return Empty container if successful, otherwise null. @@ -251,7 +257,7 @@ public abstract class FluidContainerRegistry /** * Determines the capacity of a full container. - * + * * @param container * The full container. * @return The containers capacity, or 0 if the ItemStack does not represent @@ -264,7 +270,7 @@ public abstract class FluidContainerRegistry /** * Determines the capacity of a container. - * + * * @param fluid * FluidStack containing the type of fluid the capacity should be * determined for (ignored for full containers). @@ -361,7 +367,7 @@ public abstract class FluidContainerRegistry public final ItemStack filledContainer; public final ItemStack emptyContainer; - + public FluidContainerData(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer) { this(stack, filledContainer, emptyContainer, false); diff --git a/src/main/java/net/minecraftforge/fluids/FluidRegistry.java b/src/main/java/net/minecraftforge/fluids/FluidRegistry.java index 9ec57ca2b9..09e6a9c5fa 100644 --- a/src/main/java/net/minecraftforge/fluids/FluidRegistry.java +++ b/src/main/java/net/minecraftforge/fluids/FluidRegistry.java @@ -1,19 +1,31 @@ package net.minecraftforge.fluids; -import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.logging.log4j.Level; import net.minecraft.block.Block; import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; import net.minecraft.util.StatCollector; import net.minecraftforge.common.MinecraftForge; +import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.registry.RegistryDelegate; /** * Handles Fluid registrations. Fluids MUST be registered in order to function. @@ -25,17 +37,23 @@ public abstract class FluidRegistry { static int maxID = 0; - static HashMap fluids = Maps.newHashMap(); - static BiMap fluidIDs = HashBiMap.create(); + static BiMap fluids = HashBiMap.create(); + static BiMap fluidIDs = HashBiMap.create(); + static BiMap fluidNames = HashBiMap.create(); //Caching this just makes some other calls faster static BiMap fluidBlocks; - + + // the globally unique fluid map - only used to associate non-defaults during world/server loading + static BiMap masterFluidReference = HashBiMap.create(); + static BiMap defaultFluidName = HashBiMap.create(); + static Map delegates = Maps.newHashMap(); + public static final Fluid WATER = new Fluid("water") { @Override public String getLocalizedName() { return StatCollector.translateToLocal("tile.water.name"); } }.setBlock(Blocks.water).setUnlocalizedName(Blocks.water.getUnlocalizedName()); - + public static final Fluid LAVA = new Fluid("lava") { @Override public String getLocalizedName() { @@ -57,41 +75,109 @@ public abstract class FluidRegistry * Called by Forge to prepare the ID map for server -> client sync. * Modders, DO NOT call this. */ - public static void initFluidIDs(BiMap newfluidIDs) + public static void initFluidIDs(BiMap newfluidIDs, Set defaultNames) { maxID = newfluidIDs.size(); fluidIDs.clear(); fluidIDs.putAll(newfluidIDs); + fluidNames.clear(); + for (Entry e : fluidIDs.entrySet()) { + fluidNames.put(e.getValue(), e.getKey().getName()); + } + loadFluidDefaults(defaultNames); } /** - * Register a new Fluid. If a fluid with the same name already exists, registration is denied. + * Called by forge to load default fluid IDs from the world or from server -> client for syncing + * DO NOT call this and expect useful behaviour. + */ + private static void loadFluidDefaults(Set defaultNames) + { + // If there's an empty set of default names, use the defaults as defined locally + if (defaultNames.isEmpty()) { + defaultNames.addAll(defaultFluidName.values()); + } + for (String defaultName : defaultNames) + { + Fluid fluid = masterFluidReference.get(defaultName); + if (fluid == null) { + String derivedName = defaultName.split(":",2)[1]; + String localDefault = defaultFluidName.get(derivedName); + if (localDefault == null) { + FMLLog.getLogger().log(Level.ERROR, "The fluid {} (specified as {}) is missing from this instance - it will be removed", derivedName, defaultName); + continue; + } + fluid = masterFluidReference.get(localDefault); + FMLLog.getLogger().log(Level.ERROR, "The fluid {} specified as default is not present - it will be reverted to default {}", defaultName, localDefault); + } + FMLLog.getLogger().log(Level.DEBUG, "The fluid {} has been selected as the default fluid for {}", defaultName, fluid.getName()); + Fluid oldFluid = fluids.put(fluid.getName(), fluid); + Integer id = fluidIDs.remove(oldFluid); + fluidIDs.put(fluid, id); + } + fluidBlocks = null; + for (FluidDelegate fd : delegates.values()) + { + fd.rebind(); + } + } + + /** + * Register a new Fluid. If a fluid with the same name already exists, registration the alternative fluid is tracked + * in case it is the default in another place * * @param fluid * The fluid to register. - * @return True if the fluid was successfully registered; false if there is a name clash. + * @return True if the fluid was registered as the current default fluid, false if it was only registered as an alternative */ public static boolean registerFluid(Fluid fluid) { - if (fluidIDs.containsKey(fluid.getName())) + masterFluidReference.put(uniqueName(fluid), fluid); + delegates.put(fluid, new FluidDelegate(fluid, fluid.getName())); + if (fluids.containsKey(fluid.getName())) { return false; } fluids.put(fluid.getName(), fluid); - fluidIDs.put(fluid.getName(), ++maxID); + maxID++; + fluidIDs.put(fluid, maxID); + fluidNames.put(maxID, fluid.getName()); + defaultFluidName.put(fluid.getName(), uniqueName(fluid)); MinecraftForge.EVENT_BUS.post(new FluidRegisterEvent(fluid.getName(), maxID)); return true; } + private static String uniqueName(Fluid fluid) + { + ModContainer activeModContainer = Loader.instance().activeModContainer(); + String activeModContainerName = activeModContainer == null ? "minecraft" : activeModContainer.getModId(); + return activeModContainerName+":"+fluid.getName(); + } + + /** + * Is the supplied fluid the current default fluid for it's name + * @param fluid the fluid we're testing + * @return if the fluid is default + */ + public static boolean isFluidDefault(Fluid fluid) + { + return fluids.containsValue(fluid); + } + + /** + * Does the supplied fluid have an entry for it's name (whether or not the fluid itself is default) + * @param fluid the fluid we're testing + * @return if the fluid's name has a registration entry + */ public static boolean isFluidRegistered(Fluid fluid) { - return fluidIDs.containsKey(fluid.getName()); + return fluid != null && fluids.containsKey(fluid.getName()); } public static boolean isFluidRegistered(String fluidName) { - return fluidIDs.containsKey(fluidName); + return fluids.containsKey(fluidName); } public static Fluid getFluid(String fluidName) @@ -101,31 +187,42 @@ public abstract class FluidRegistry public static Fluid getFluid(int fluidID) { - return fluids.get(getFluidName(fluidID)); + return fluidIDs.inverse().get(fluidID); } - public static String getFluidName(int fluidID) + public static int getFluidID(Fluid fluid) { - return fluidIDs.inverse().get(fluidID); - } - - public static String getFluidName(FluidStack stack) - { - return getFluidName(stack.fluidID); + return fluidIDs.get(fluid); } public static int getFluidID(String fluidName) { - return fluidIDs.get(fluidName); + return fluidIDs.get(getFluid(fluidName)); + } + + @Deprecated //Remove in 1.8.3 + public static String getFluidName(int fluidID) + { + return fluidNames.get(fluidID); + } + + public static String getFluidName(Fluid fluid) + { + return fluids.inverse().get(fluid); + } + + public static String getFluidName(FluidStack stack) + { + return getFluidName(stack.getFluid()); } public static FluidStack getFluidStack(String fluidName, int amount) { - if (!fluidIDs.containsKey(fluidName)) + if (!fluids.containsKey(fluidName)) { return null; } - return new FluidStack(getFluidID(fluidName), amount); + return new FluidStack(getFluid(fluidName), amount); } /** @@ -139,7 +236,17 @@ public abstract class FluidRegistry /** * Returns a read-only map containing Fluid Names and their associated IDs. */ + @Deprecated //Change return type to in 1.8.3 public static Map getRegisteredFluidIDs() + { + return ImmutableMap.copyOf(fluidNames.inverse()); + } + + /** + * Returns a read-only map containing Fluid IDs and their associated Fluids. + * In 1.8.3, this will change to just 'getRegisteredFluidIDs' + */ + public static Map getRegisteredFluidIDsByFluid() { return ImmutableMap.copyOf(fluidIDs); } @@ -160,7 +267,7 @@ public abstract class FluidRegistry } return fluidBlocks.get(block); } - + public static class FluidRegisterEvent extends Event { public final String fluidName; @@ -177,4 +284,110 @@ public abstract class FluidRegistry { return maxID; } -} + + public static String getDefaultFluidName(Fluid key) + { + String name = masterFluidReference.inverse().get(key); + if (Strings.isNullOrEmpty(name)) { + FMLLog.getLogger().log(Level.ERROR, "The fluid registry is corrupted. A fluid {} {} is not properly registered. The mod that registered this is broken", key.getClass().getName(), key.getName()); + throw new IllegalStateException("The fluid registry is corrupted"); + } + return name; + } + + public static void loadFluidDefaults(NBTTagCompound tag) + { + Set defaults = Sets.newHashSet(); + if (tag.hasKey("DefaultFluidList",9)) + { + FMLLog.getLogger().log(Level.DEBUG, "Loading persistent fluid defaults from world"); + NBTTagList tl = tag.getTagList("DefaultFluidList", 8); + for (int i = 0; i < tl.tagCount(); i++) + { + defaults.add(tl.getStringTagAt(i)); + } + } + else + { + FMLLog.getLogger().log(Level.DEBUG, "World is missing persistent fluid defaults - using local defaults"); + } + loadFluidDefaults(defaults); + } + + public static void writeDefaultFluidList(NBTTagCompound forgeData) + { + NBTTagList tagList = new NBTTagList(); + + for (Entry def : fluids.entrySet()) + { + tagList.appendTag(new NBTTagString(getDefaultFluidName(def.getValue()))); + } + + forgeData.setTag("DefaultFluidList", tagList); + } + + public static void validateFluidRegistry() + { + Set illegalFluids = Sets.newHashSet(); + for (Fluid f : fluids.values()) + { + if (!masterFluidReference.containsValue(f)) + { + illegalFluids.add(f); + } + } + + if (!illegalFluids.isEmpty()) + { + FMLLog.getLogger().log(Level.FATAL, "The fluid registry is corrupted. Something has inserted a fluid without registering it"); + FMLLog.getLogger().log(Level.FATAL, "There is {} unregistered fluids", illegalFluids.size()); + for (Fluid f: illegalFluids) + { + FMLLog.getLogger().log(Level.FATAL, " Fluid name : {}, type: {}", f.getName(), f.getClass().getName()); + } + FMLLog.getLogger().log(Level.FATAL, "The mods that own these fluids need to register them properly"); + throw new IllegalStateException("The fluid map contains fluids unknown to the master fluid registry"); + } + } + + static RegistryDelegate makeDelegate(Fluid fl) + { + return delegates.get(fl); + } + + + private static class FluidDelegate implements RegistryDelegate + { + private String name; + private Fluid fluid; + + FluidDelegate(Fluid fluid, String name) + { + this.fluid = fluid; + this.name = name; + } + + @Override + public Fluid get() + { + return fluid; + } + + @Override + public String name() + { + return name; + } + + @Override + public Class type() + { + return Fluid.class; + } + + void rebind() + { + fluid = fluids.get(name); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/fluids/FluidStack.java b/src/main/java/net/minecraftforge/fluids/FluidStack.java index 4de7a2609e..053e7ba449 100644 --- a/src/main/java/net/minecraftforge/fluids/FluidStack.java +++ b/src/main/java/net/minecraftforge/fluids/FluidStack.java @@ -1,10 +1,10 @@ package net.minecraftforge.fluids; -import java.util.Locale; -import com.google.common.base.Strings; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.registry.RegistryDelegate; /** * ItemStack substitute for Fluids. @@ -18,25 +18,36 @@ import net.minecraft.nbt.NBTTagCompound; */ public class FluidStack { - public int fluidID; + /** + * This field will be removed in 1.8. It may be incorrect after a world is loaded. Code should always + * use {@link #getFluid()} instead. That will always reflect the correct value. + */ + @Deprecated + public final Fluid fluid; public int amount; public NBTTagCompound tag; + private RegistryDelegate fluidDelegate; public FluidStack(Fluid fluid, int amount) { - this.fluidID = fluid.getID(); + if (fluid == null) + { + FMLLog.bigWarning("Null fluid supplied to fluidstack. Did you try and create a stack for an unregistered fluid?"); + throw new IllegalArgumentException("Cannot create a fluidstack from a null fluid"); + } + else if (!FluidRegistry.isFluidRegistered(fluid)) + { + FMLLog.bigWarning("Failed attempt to create a FluidStack for an unregistered Fluid %s (type %s)", fluid.getName(), fluid.getClass().getName()); + throw new IllegalArgumentException("Cannot create a fluidstack from an unregistered fluid"); + } + this.fluidDelegate = FluidRegistry.makeDelegate(fluid); this.amount = amount; + this.fluid = fluid; } - public FluidStack(int fluidID, int amount) + public FluidStack(Fluid fluid, int amount, NBTTagCompound nbt) { - this.fluidID = fluidID; - this.amount = amount; - } - - public FluidStack(int fluidID, int amount, NBTTagCompound nbt) - { - this(fluidID, amount); + this(fluid, amount); if (nbt != null) { @@ -46,7 +57,21 @@ public class FluidStack public FluidStack(FluidStack stack, int amount) { - this(stack.fluidID, amount, stack.tag); + this(stack.getFluid(), amount, stack.tag); + } + + // To be removed in 1.8 + @Deprecated + public FluidStack(int fluidID, int amount) + { + this(FluidRegistry.getFluid(fluidID), amount); + } + + // To be removed in 1.8 + @Deprecated + public FluidStack(int fluidID, int amount, NBTTagCompound nbt) + { + this(FluidRegistry.getFluid(fluidID), amount, nbt); } /** @@ -60,32 +85,23 @@ public class FluidStack return null; } String fluidName = nbt.getString("FluidName"); - if (Strings.isNullOrEmpty(fluidName)) - { - fluidName = nbt.hasKey("LiquidName") ? nbt.getString("LiquidName").toLowerCase(Locale.ENGLISH) : null; - fluidName = Fluid.convertLegacyName(fluidName); - } - if (fluidName ==null || FluidRegistry.getFluid(fluidName) == null) + if (fluidName == null || FluidRegistry.getFluid(fluidName) == null) { return null; } - FluidStack stack = new FluidStack(FluidRegistry.getFluidID(fluidName), nbt.getInteger("Amount")); + FluidStack stack = new FluidStack(FluidRegistry.getFluid(fluidName), nbt.getInteger("Amount")); if (nbt.hasKey("Tag")) { stack.tag = nbt.getCompoundTag("Tag"); } - else if (nbt.hasKey("extra")) - { - stack.tag = nbt.getCompoundTag("extra"); - } return stack; } public NBTTagCompound writeToNBT(NBTTagCompound nbt) { - nbt.setString("FluidName", FluidRegistry.getFluidName(fluidID)); + nbt.setString("FluidName", FluidRegistry.getFluidName(getFluid())); nbt.setInteger("Amount", amount); if (tag != null) @@ -97,7 +113,12 @@ public class FluidStack public final Fluid getFluid() { - return FluidRegistry.getFluid(fluidID); + return fluidDelegate.get(); + } + + public final int getFluidID() + { + return FluidRegistry.getFluidID(getFluid()); } public String getLocalizedName() @@ -115,7 +136,7 @@ public class FluidStack */ public FluidStack copy() { - return new FluidStack(fluidID, amount, tag); + return new FluidStack(getFluid(), amount, tag); } /** @@ -127,7 +148,7 @@ public class FluidStack */ public boolean isFluidEqual(FluidStack other) { - return other != null && fluidID == other.fluidID && isFluidStackTagEqual(other); + return other != null && getFluid() == other.getFluid() && isFluidStackTagEqual(other); } private boolean isFluidStackTagEqual(FluidStack other) @@ -192,7 +213,12 @@ public class FluidStack @Override public final int hashCode() { - return fluidID; + int code = 1; + code = 31*code + getFluid().hashCode(); + code = 31*code + amount; + if (tag != null) + code = 31*code + tag.hashCode(); + return code; } /**