package net.minecraftforge.fluids; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.eventhandler.Event; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; 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, // but the external interface for checking ItemStacks will still exist. private static class ContainerKey { ItemStack container; FluidStack stack; private ContainerKey(ItemStack container) { this.container = container; } private ContainerKey(ItemStack container, FluidStack stack) { this(container); this.stack = stack; } @Override public int hashCode() { int code = 1; code = 31*code + container.getItem().hashCode(); code = 31*code + container.getMetadata(); if (stack != null) code = 31*code + stack.getFluid().hashCode(); return code; } @Override public boolean equals(Object o) { if (!(o instanceof ContainerKey)) return false; ContainerKey ck = (ContainerKey)o; if (container.getItem() != ck.container.getItem()) return false; if (container.getMetadata() != ck.container.getMetadata()) return false; if (stack == null && ck.stack != null) return false; if (stack != null && ck.stack == null) return false; if (stack == null && ck.stack == null) return true; if (stack.getFluid() != ck.stack.getFluid()) return false; return true; } } private static Map<ContainerKey, FluidContainerData> containerFluidMap = Maps.newHashMap(); private static Map<ContainerKey, FluidContainerData> filledContainerMap = Maps.newHashMap(); private static Set<ContainerKey> emptyContainers = Sets.newHashSet(); public static final int BUCKET_VOLUME = 1000; public static final ItemStack EMPTY_BUCKET = new ItemStack(Items.bucket); public static final ItemStack EMPTY_BOTTLE = new ItemStack(Items.glass_bottle); private static final ItemStack NULL_EMPTYCONTAINER = new ItemStack(Items.bucket); static { registerFluidContainer(FluidRegistry.WATER, new ItemStack(Items.water_bucket), EMPTY_BUCKET); registerFluidContainer(FluidRegistry.LAVA, new ItemStack(Items.lava_bucket), EMPTY_BUCKET); registerFluidContainer(FluidRegistry.WATER, new ItemStack(Items.potionitem), EMPTY_BOTTLE); } private FluidContainerRegistry(){} /** * Register a new fluid containing item. * * @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. * @param emptyContainer * ItemStack representing the container when it is empty. * @return True if container was successfully registered; false if it already is. */ public static boolean registerFluidContainer(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer) { return registerFluidContainer(new FluidContainerData(stack, filledContainer, emptyContainer)); } /** * 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 * ItemStack representing the container when it is full. * @param emptyContainer * ItemStack representing the container when it is empty. * @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, ItemStack emptyContainer) { if (!FluidRegistry.isFluidRegistered(fluid)) { FluidRegistry.registerFluid(fluid); } return registerFluidContainer(new FluidStack(fluid, BUCKET_VOLUME), filledContainer, emptyContainer); } /** * 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, or an invalid parameter was passed. */ public static boolean registerFluidContainer(FluidStack stack, ItemStack filledContainer) { return registerFluidContainer(new FluidContainerData(stack, filledContainer, null, true)); } /** * 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, or an invalid parameter was passed. */ public static boolean registerFluidContainer(Fluid fluid, ItemStack filledContainer) { if (!FluidRegistry.isFluidRegistered(fluid)) { FluidRegistry.registerFluid(fluid); } return registerFluidContainer(new FluidStack(fluid, BUCKET_VOLUME), filledContainer); } /** * Register a new fluid containing item. * * @param data * See {@link FluidContainerData}. * @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) || 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) { filledContainerMap.put(new ContainerKey(data.emptyContainer, data.fluid), data); emptyContainers.add(new ContainerKey(data.emptyContainer)); } MinecraftForge.EVENT_BUS.post(new FluidContainerRegisterEvent(data)); return true; } /** * Determines the fluid type and amount inside a container. * * @param container * The fluid container. * @return FluidStack representing stored fluid. */ public static FluidStack getFluidForFilledItem(ItemStack container) { if (container == null) { return null; } FluidContainerData data = containerFluidMap.get(new ContainerKey(container)); return data == null ? null : data.fluid.copy(); } /** * 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 * ItemStack representing the empty container. * @return Filled container if successful, otherwise null. */ public static ItemStack fillFluidContainer(FluidStack fluid, ItemStack container) { if (container == null || fluid == null) { return null; } FluidContainerData data = filledContainerMap.get(new ContainerKey(container, fluid)); if (data != null && fluid.amount >= data.fluid.amount) { return data.filledContainer.copy(); } return null; } /** * Attempts to empty a full container. * * @param container * ItemStack representing the full container. * @return Empty container if successful, otherwise null. */ public static ItemStack drainFluidContainer(ItemStack container) { if (container == null) { return null; } FluidContainerData data = containerFluidMap.get(new ContainerKey(container)); if (data != null) { return data.emptyContainer.copy(); } return null; } /** * Determines the capacity of a full container. * * @param container * The full container. * @return The containers capacity, or 0 if the ItemStack does not represent * a registered container. */ public static int getContainerCapacity(ItemStack container) { return getContainerCapacity(null, container); } /** * Determines the capacity of a container. * * @param fluid * FluidStack containing the type of fluid the capacity should be * determined for (ignored for full containers). * @param container * The container (full or empty). * @return The containers capacity, or 0 if the ItemStack does not represent * a registered container or the FluidStack is not registered with * the empty container. */ public static int getContainerCapacity(FluidStack fluid, ItemStack container) { if (container == null) { return 0; } FluidContainerData data = containerFluidMap.get(new ContainerKey(container)); if (data != null) { return data.fluid.amount; } if (fluid != null) { data = filledContainerMap.get(new ContainerKey(container, fluid)); if (data != null) { return data.fluid.amount; } } return 0; } /** * Determines if a container holds a specific fluid. */ public static boolean containsFluid(ItemStack container, FluidStack fluid) { if (container == null || fluid == null) { return false; } FluidContainerData data = containerFluidMap.get(new ContainerKey(container)); return data == null ? false : data.fluid.containsFluid(fluid); } public static boolean isBucket(ItemStack container) { if (container == null) { return false; } if (container.isItemEqual(EMPTY_BUCKET)) { return true; } FluidContainerData data = containerFluidMap.get(new ContainerKey(container)); return data != null && data.emptyContainer.isItemEqual(EMPTY_BUCKET); } public static boolean isContainer(ItemStack container) { return isEmptyContainer(container) || isFilledContainer(container); } public static boolean isEmptyContainer(ItemStack container) { return container != null && emptyContainers.contains(new ContainerKey(container)); } public static boolean isFilledContainer(ItemStack container) { return container != null && getFluidForFilledItem(container) != null; } public static FluidContainerData[] getRegisteredFluidContainerData() { return containerFluidMap.values().toArray(new FluidContainerData[containerFluidMap.size()]); } /** * Wrapper class for the registry entries. Ensures that none of the attempted registrations * contain null references unless permitted. */ public static class FluidContainerData { public final FluidStack fluid; public final ItemStack filledContainer; public final ItemStack emptyContainer; public FluidContainerData(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer) { this(stack, filledContainer, emptyContainer, false); } public FluidContainerData(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer, boolean nullEmpty) { this.fluid = stack; this.filledContainer = filledContainer; this.emptyContainer = emptyContainer == null ? NULL_EMPTYCONTAINER : emptyContainer; if (stack == null || filledContainer == null || emptyContainer == null && !nullEmpty) { throw new RuntimeException("Invalid FluidContainerData - a parameter was null."); } } public FluidContainerData copy() { return new FluidContainerData(fluid, filledContainer, emptyContainer, true); } } public static class FluidContainerRegisterEvent extends Event { public final FluidContainerData data; public FluidContainerRegisterEvent(FluidContainerData data) { this.data = data.copy(); } } }