/* * Minecraft Forge * Copyright (c) 2016-2018. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.fluids; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.minecraftforge.fml.ModThreadContext; 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.ResourceLocation; import net.minecraft.util.text.translation.I18n; 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.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.registries.IRegistryDelegate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import javax.annotation.Nullable; /** * Handles Fluid registrations. Fluids MUST be registered in order to function. */ public abstract class FluidRegistry { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker FLUIDS = MarkerManager.getMarker("FLUIDS"); static int maxID = 0; 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(); static boolean universalBucketEnabled = false; static Set bucketFluids = Sets.newHashSet(); public static final Fluid WATER = new Fluid("water", new ResourceLocation("blocks/water_still"), new ResourceLocation("blocks/water_flow"), new ResourceLocation("blocks/water_overlay")) { @Override public String getLocalizedName(FluidStack fs) { return I18n.translateToLocal("tile.water.name"); } }.setBlock(Blocks.WATER).setUnlocalizedName(Blocks.WATER.getUnlocalizedName()); public static final Fluid LAVA = new Fluid("lava", new ResourceLocation("blocks/lava_still"), new ResourceLocation("blocks/lava_flow")) { @Override public String getLocalizedName(FluidStack fs) { return I18n.translateToLocal("tile.lava.name"); } }.setBlock(Blocks.LAVA).setLuminosity(15).setDensity(3000).setViscosity(6000).setTemperature(1300).setUnlocalizedName(Blocks.LAVA.getUnlocalizedName()); static { registerFluid(WATER); registerFluid(LAVA); } private FluidRegistry(){} /** * Called by Forge to prepare the ID map for server -> client sync. * Modders, DO NOT call this. */ public static void initFluidIDs(BiMap newfluidIDs, Set defaultNames) { maxID = newfluidIDs.size(); loadFluidDefaults(newfluidIDs, defaultNames); } /** * 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. * @param localFluidIDs * @param defaultNames */ private static void loadFluidDefaults(BiMap localFluidIDs, Set defaultNames) { // If there's an empty set of default names, use the defaults as defined locally if (defaultNames.isEmpty()) { defaultNames.addAll(defaultFluidName.values()); } BiMap localFluids = HashBiMap.create(fluids); 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) { LOGGER.error(FLUIDS, "The fluid {} (specified as {}) is missing from this instance - it will be removed",derivedName,defaultName); continue; } fluid = masterFluidReference.get(localDefault); LOGGER.error(FLUIDS, "The fluid {} specified as default is not present - it will be reverted to default {}",defaultName,localDefault); } LOGGER.debug(FLUIDS, "The fluid {} has been selected as the default fluid for {}",defaultName,fluid.getName()); Fluid oldFluid = localFluids.put(fluid.getName(), fluid); Integer id = localFluidIDs.remove(oldFluid); localFluidIDs.put(fluid, id); } BiMap localFluidNames = HashBiMap.create(); for (Entry e : localFluidIDs.entrySet()) { localFluidNames.put(e.getValue(), e.getKey().getName()); } fluidIDs = localFluidIDs; fluids = localFluids; fluidNames = localFluidNames; 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 registered as the current default fluid, false if it was only registered as an alternative */ public static boolean registerFluid(Fluid fluid) { 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); 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) { return ModThreadContext.get().getActiveContainer().getPrefix() +":"+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 fluid != null && fluids.containsKey(fluid.getName()); } public static boolean isFluidRegistered(String fluidName) { return fluids.containsKey(fluidName); } public static Fluid getFluid(String fluidName) { return fluids.get(fluidName); } public static String getFluidName(Fluid fluid) { return fluids.inverse().get(fluid); } public static String getFluidName(FluidStack stack) { return getFluidName(stack.getFluid()); } @Nullable public static FluidStack getFluidStack(String fluidName, int amount) { if (!fluids.containsKey(fluidName)) { return null; } return new FluidStack(getFluid(fluidName), amount); } /** * Returns a read-only map containing Fluid Names and their associated Fluids. */ public static Map getRegisteredFluids() { return ImmutableMap.copyOf(fluids); } /** * Returns a read-only map containing Fluid Names and their associated IDs. * Modders should never actually use this, use the String names. */ @Deprecated public static Map getRegisteredFluidIDs() { return ImmutableMap.copyOf(fluidIDs); } /** * Enables the universal bucket in forge. * Has to be called before pre-initialization. * Actually just call it statically in your mod class. */ public static void enableUniversalBucket() { universalBucketEnabled = true; } public static boolean isUniversalBucketEnabled() { return universalBucketEnabled; } /** * Registers a fluid with the universal bucket. * This only has an effect if the universal bucket is enabled. * @param fluid The fluid that the bucket shall be able to hold * @return True if the fluid was added successfully, false if it already was registered or couldn't be registered with the bucket. */ public static boolean addBucketForFluid(Fluid fluid) { if(fluid == null) { return false; } // register unregistered fluids if (!isFluidRegistered(fluid)) { registerFluid(fluid); } return bucketFluids.add(fluid); } /** * All fluids registered with the universal bucket * @return An immutable set containing the fluids */ public static Set getBucketFluids() { return Collections.unmodifiableSet(bucketFluids); } public static Fluid lookupFluidForBlock(Block block) { if (fluidBlocks == null) { BiMap tmp = HashBiMap.create(); for (Fluid fluid : fluids.values()) { if (fluid.canBePlacedInWorld() && fluid.getBlock() != null) { tmp.put(fluid.getBlock(), fluid); } } fluidBlocks = tmp; } if (block == Blocks.FLOWING_WATER) { block = Blocks.WATER; } else if (block == Blocks.FLOWING_LAVA) { block = Blocks.LAVA; } return fluidBlocks.get(block); } public static class FluidRegisterEvent extends Event { private final String fluidName; private final int fluidID; public FluidRegisterEvent(String fluidName, int fluidID) { this.fluidName = fluidName; this.fluidID = fluidID; } public String getFluidName() { return fluidName; } public int getFluidID() { return fluidID; } } public static int getMaxID() { return maxID; } public static String getDefaultFluidName(Fluid key) { String name = masterFluidReference.inverse().get(key); if (Strings.isNullOrEmpty(name)) { LOGGER.error(FLUIDS, "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; } @Nullable public static String getModId(@Nullable FluidStack fluidStack) { if (fluidStack != null) { String defaultFluidName = getDefaultFluidName(fluidStack.getFluid()); if (defaultFluidName != null) { ResourceLocation fluidResourceName = new ResourceLocation(defaultFluidName); return fluidResourceName.getResourceDomain(); } } return null; } public static void loadFluidDefaults(NBTTagCompound tag) { Set defaults = Sets.newHashSet(); if (tag.hasKey("DefaultFluidList",9)) { LOGGER.debug(FLUIDS, "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 { LOGGER.debug(FLUIDS,"World is missing persistent fluid defaults - using local defaults"); } loadFluidDefaults(HashBiMap.create(fluidIDs), 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()) { LOGGER.fatal(FLUIDS, "The fluid registry is corrupted. Something has inserted a fluid without registering it"); LOGGER.fatal(FLUIDS, "There is {} unregistered fluids",illegalFluids.size()); for (Fluid f: illegalFluids) { LOGGER.fatal(FLUIDS, " Fluid name : {}, type: {}",f.getName(),f.getClass().getName()); } LOGGER.fatal(FLUIDS, "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 IRegistryDelegate makeDelegate(Fluid fl) { return delegates.get(fl); } private static class FluidDelegate implements IRegistryDelegate { 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 ResourceLocation name() { return new ResourceLocation(name); } @Override public Class type() { return Fluid.class; } void rebind() { fluid = fluids.get(name); } } }