aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/api/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/api/util')
-rw-r--r--src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java440
-rw-r--r--src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java437
-rw-r--r--src/main/java/gregtech/api/util/GT_CoverBehavior.java202
-rw-r--r--src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java482
-rw-r--r--src/main/java/gregtech/api/util/GT_LanguageManager.java5
-rw-r--r--src/main/java/gregtech/api/util/GT_ModHandler.java2
-rw-r--r--src/main/java/gregtech/api/util/GT_OreDictUnificator.java37
-rw-r--r--src/main/java/gregtech/api/util/GT_Recipe.java114
-rw-r--r--src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java284
-rw-r--r--src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java212
-rw-r--r--src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java61
-rw-r--r--src/main/java/gregtech/api/util/GT_Utility.java159
-rw-r--r--src/main/java/gregtech/api/util/ISerializableObject.java150
13 files changed, 2490 insertions, 95 deletions
diff --git a/src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java b/src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java
new file mode 100644
index 0000000000..f35f1962d1
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java
@@ -0,0 +1,440 @@
+package gregtech.api.util;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+
+import java.util.HashMap;
+
+import cpw.mods.fml.common.FMLCommonHandler;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.util.GT_Recipe.GT_Recipe_AssemblyLine;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.nbt.NBTTagString;
+import net.minecraftforge.fluids.FluidStack;
+import scala.actors.threadpool.Arrays;
+
+public class GT_AssemblyLineUtils {
+
+ /**
+ * A cache of Recipes using the Output as Key.
+ */
+ private static HashMap<GT_ItemStack, GT_Recipe_AssemblyLine> sRecipeCacheByOutput = new HashMap<GT_ItemStack, GT_Recipe_AssemblyLine>();
+ /**
+ * A cache of Recipes using the Recipe Hash String as Key.
+ */
+ private static HashMap<String, GT_Recipe_AssemblyLine> sRecipeCacheByRecipeHash = new HashMap<String, GT_Recipe_AssemblyLine>();
+
+
+
+ /**
+ * Checks the DataStick for deprecated/invalid recipes, updating them as required.
+ * @param aDataStick - The DataStick to process
+ * @return Is this DataStick now valid with a current recipe?
+ */
+ public static boolean processDataStick(ItemStack aDataStick) {
+ if (!isItemDataStick(aDataStick)) {
+ return false;
+ }
+ if (doesDataStickNeedUpdate(aDataStick)) {
+ ItemStack aStickOutput = getDataStickOutput(aDataStick);
+ if (aStickOutput != null) {
+ GT_Recipe_AssemblyLine aIntendedRecipe = findAssemblyLineRecipeByOutput(aStickOutput);
+ if (aIntendedRecipe != null) {
+ return setAssemblyLineRecipeOnDataStick(aDataStick, aIntendedRecipe);
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Finds an Assembly Line recipe from a DataStick.
+ * @param aDataStick - The DataStick to check.
+ * @return The GT_Recipe_AssemblyLine recipe contained on the DataStick, if any.
+ */
+ public static GT_Recipe_AssemblyLine findAssemblyLineRecipeFromDataStick(ItemStack aDataStick) {
+ return findAssemblyLineRecipeFromDataStick(aDataStick, false);
+ }
+
+ /**
+ * Finds an Assembly Line recipe from a DataStick.
+ * @param aDataStick - The DataStick to check.
+ * @param aReturnBuiltRecipe - Do we return a GT_Recipe_AssemblyLine built from the data on the Data Stick instead of searching the Recipe Map?
+ * @return The GT_Recipe_AssemblyLine recipe contained on the DataStick, if any.
+ */
+ public static GT_Recipe_AssemblyLine findAssemblyLineRecipeFromDataStick(ItemStack aDataStick, boolean aReturnBuiltRecipe) {
+ if (!isItemDataStick(aDataStick)) {
+ return null;
+ }
+ ItemStack[] aInputs = new ItemStack[15];
+ ItemStack[] aOutputs = new ItemStack[1];
+ FluidStack[] aFluidInputs = new FluidStack[4];
+
+ NBTTagCompound aTag = aDataStick.getTagCompound();
+ if (aTag == null) {
+ return null;
+ }
+
+ //Get From Cache
+ if (doesDataStickHaveRecipeHash(aDataStick)) {
+ GT_Recipe_AssemblyLine aRecipeFromCache = sRecipeCacheByRecipeHash.get(getHashFromDataStack(aDataStick));
+ if (aRecipeFromCache != null) {
+ return aRecipeFromCache;
+ }
+ }
+
+ for (int i = 0; i < 15; i++) {
+ int count = aTag.getInteger("a" + i);
+ if (!aTag.hasKey("" + i) && count <= 0) {
+ continue;
+ }
+
+ boolean flag = true;
+ if (count > 0) {
+ for (int j = 0; j < count; j++) {
+ aInputs[i] = GT_Utility.loadItem(aTag, "a" + i + ":" + j);
+ if (aInputs[i] == null) {
+ continue;
+ }
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Item " + i + " : " + aInputs[i].getUnlocalizedName());
+ }
+ flag = false;
+ break;
+ }
+ }
+ if (flag) {
+ aInputs[i] = GT_Utility.loadItem(aTag, "" + i);
+ if (aInputs[i] == null) {
+ flag = false;
+ continue;
+ }
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Item " + i + " : " + aInputs[i].getUnlocalizedName());
+ }
+ flag = false;
+ }
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info(i + (flag ? " not accepted" : " accepted"));
+ }
+ }
+
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("All Items done, start fluid check");
+ }
+ for (int i = 0; i < 4; i++) {
+ if (!aTag.hasKey("f" + i)) continue;
+ aFluidInputs[i] = GT_Utility.loadFluid(aTag, "f" + i);
+ if (aFluidInputs[i] == null) continue;
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Fluid " + i + " " + aFluidInputs[i].getUnlocalizedName());
+ }
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info(i + " accepted");
+ }
+ }
+ aOutputs = new ItemStack[]{GT_Utility.loadItem(aTag, "output")};
+ if (!aTag.hasKey("output") || !aTag.hasKey("time") || aTag.getInteger("time") <= 0 || !aTag.hasKey("eu") || aOutputs[0] == null || !GT_Utility.isStackValid(aOutputs[0])) {
+ return null;
+ }
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Found Data Stick recipe");
+ }
+
+ int aTime = aTag.getInteger("time");
+ int aEU = aTag.getInteger("eu");
+
+ // Try build a recipe instance
+ if (aReturnBuiltRecipe) {
+ GT_Recipe_AssemblyLine aBuiltRecipe = new GT_Recipe_AssemblyLine(null, 0, aInputs, aFluidInputs, aOutputs[0], aTime, aEU);
+ return aBuiltRecipe;
+ }
+
+
+ for (GT_Recipe_AssemblyLine aRecipe : GT_Recipe.GT_Recipe_AssemblyLine.sAssemblylineRecipes) {
+ if (aRecipe.mEUt == aEU && aRecipe.mDuration == aTime) {
+ if (GT_Utility.areStacksEqual(aOutputs[0], aRecipe.mOutput, true)) {
+ if (Arrays.equals(aRecipe.mInputs, aInputs) && Arrays.equals(aRecipe.mFluidInputs, aFluidInputs)) {
+ // Cache it
+ String aRecipeHash = generateRecipeHash(aRecipe);
+ sRecipeCacheByRecipeHash.put(aRecipeHash, aRecipe);
+ sRecipeCacheByOutput.put(new GT_ItemStack(aRecipe.mOutput), aRecipe);
+ return aRecipe;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Finds a GT_Recipe_AssemblyLine based on the expected output ItemStack.
+ * @param aOutput - The Output of a GT_Recipe_AssemblyLine.
+ * @return First found GT_Recipe_AssemblyLine with matching output.
+ */
+ public static GT_Recipe_AssemblyLine findAssemblyLineRecipeByOutput(ItemStack aOutput) {
+ if (aOutput == null) {
+ return null;
+ }
+
+ // Check the cache
+ GT_ItemStack aCacheStack = new GT_ItemStack(aOutput);
+ GT_Recipe_AssemblyLine aRecipeFromCache = sRecipeCacheByOutput.get(aCacheStack);
+ if (aRecipeFromCache != null) {
+ return aRecipeFromCache;
+ }
+
+ // Iterate all recipes and return the first matching based on Output.
+ for (GT_Recipe_AssemblyLine aRecipe : GT_Recipe.GT_Recipe_AssemblyLine.sAssemblylineRecipes) {
+ ItemStack aRecipeOutput = aRecipe.mOutput;
+ if (GT_Utility.areStacksEqual(aRecipeOutput, aOutput)) {
+ // Cache it to prevent future iterations of all recipes
+ sRecipeCacheByOutput.put(aCacheStack, aRecipe);
+ sRecipeCacheByRecipeHash.put(generateRecipeHash(aRecipe), aRecipe);
+ return aRecipe;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param aRecipe - The recipe to generate a Recipe Hash String from.
+ * @return The Recipe Hash String.
+ */
+ public static String generateRecipeHash(GT_Recipe_AssemblyLine aRecipe) {
+ String aHash = "Invalid.Recipe.Hash";
+ if (aRecipe != null) {
+ aHash = "Hash."+aRecipe.hashCode();
+ }
+ return aHash;
+ }
+
+ /**
+ * @param aHash - Recipe hash String, may be null but will just be treated as invalid.
+ * @return Is this Recipe Hash String valid?
+ */
+ public static boolean isValidHash(String aHash) {
+ if (aHash != null && aHash.length() > 0) {
+ if (!aHash.equals("Invalid.Recipe.Hash") && aHash.contains("Hash.")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param aStack - The ItemStack to check.
+ * @return Is this ItemStack a Data Stick?
+ */
+ public static boolean isItemDataStick(ItemStack aStack) {
+ return GT_Utility.isStackValid(aStack) && ItemList.Tool_DataStick.isStackEqual(aStack, false, true);
+ }
+
+ /**
+ * @param aDataStick - The Data Stick to check.
+ * @return Does this Data Stick have a valid output ItemStack?
+ */
+ public static boolean doesDataStickHaveOutput(ItemStack aDataStick) {
+ if (isItemDataStick(aDataStick) && aDataStick.hasTagCompound() && aDataStick.getTagCompound().hasKey("output")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param aDataStick - The Data Stick to check.
+ * @return Does this Data Stick need recipe data updated.
+ */
+ public static boolean doesDataStickNeedUpdate(ItemStack aDataStick) {
+ if (isItemDataStick(aDataStick) && doesDataStickHaveRecipeHash(aDataStick)) {
+ String aStickHash = getHashFromDataStack(aDataStick);
+ if (isValidHash(aStickHash) && doesDataStickHaveOutput(aDataStick)) {
+ ItemStack aStickOutput = getDataStickOutput(aDataStick);
+ GT_Recipe_AssemblyLine aIntendedRecipe = findAssemblyLineRecipeByOutput(aStickOutput);
+ if (aStickHash.equals(generateRecipeHash(aIntendedRecipe))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param aDataStick - The Data Stick to check.
+ * @return Does this have a Recipe Hash String at all?
+ */
+ public static boolean doesDataStickHaveRecipeHash(ItemStack aDataStick) {
+ if (isItemDataStick(aDataStick) && aDataStick.hasTagCompound()) {
+ NBTTagCompound aNBT = aDataStick.getTagCompound();
+ if (aNBT.hasKey("Data.Recipe.Hash")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the Output ItemStack from a Data Stick.
+ * @param aDataStick - The Data Stick to check.
+ * @return Output ItemStack contained on the Data Stick.
+ */
+ public static ItemStack getDataStickOutput(ItemStack aDataStick) {
+ if (doesDataStickHaveOutput(aDataStick)) {
+ ItemStack aOutput = GT_Utility.loadItem(aDataStick.getTagCompound(), "output");
+ return aOutput;
+ }
+ return null;
+ }
+
+ /**
+ * @param aDataStick - The Data Stick to procces.
+ * @return The stored Recipe Hash String on the Data Stick, will return an invalid Hash if one is not found. <p>
+ * Check with isValidHash(). <p>
+ * Will not return Null.
+ */
+ public static String getHashFromDataStack(ItemStack aDataStick) {
+ if (isItemDataStick(aDataStick) && aDataStick.hasTagCompound()) {
+ NBTTagCompound aNBT = aDataStick.getTagCompound();
+ if (aNBT.hasKey("Data.Recipe.Hash")) {
+ return aNBT.getString("Data.Recipe.Hash");
+
+ }
+ }
+ return "Invalid.Recipe.Hash";
+ }
+
+ /**
+ *
+ * @param aDataStick - The Data Stick to update.
+ * @param aRecipeHash - The Recipe Hash String to update with.
+ * @return Did we update the Recipe Hash String on the Data Stick?
+ */
+ public static boolean setRecipeHashOnDataStick(ItemStack aDataStick, String aRecipeHash) {
+ if (isItemDataStick(aDataStick) && aDataStick.hasTagCompound()) {
+ NBTTagCompound aNBT = aDataStick.getTagCompound();
+ aNBT.setString("Data.Recipe.Hash", aRecipeHash);
+ aDataStick.setTagCompound(aNBT);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param aDataStick - The Data Stick to update.
+ * @param aNewRecipe - The New GT_Recipe_AssemblyLine recipe to update it with.
+ * @return Did we set the new recipe data & Recipe Hash String on the Data Stick?
+ */
+ public static boolean setAssemblyLineRecipeOnDataStick(ItemStack aDataStick, GT_Recipe_AssemblyLine aNewRecipe) {
+ if (isItemDataStick(aDataStick)) {
+ String s = aNewRecipe.mOutput.getDisplayName();
+ if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
+ s = GT_Assemblyline_Server.lServerNames.get(aNewRecipe.mOutput.getDisplayName());
+ if (s == null) {
+ s = aNewRecipe.mOutput.getDisplayName();
+ }
+ }
+
+ String aHash = generateRecipeHash(aNewRecipe);
+ if (GT_Values.D1) {
+ GT_Recipe_AssemblyLine aOldRecipe = findAssemblyLineRecipeFromDataStick(aDataStick, true);
+ GT_FML_LOGGER.info("Updating data stick: "+aDataStick.getDisplayName()+" | Old Recipe Hash: "+generateRecipeHash(aOldRecipe)+", New Recipe Hash: "+aHash);
+ }
+
+
+ //remove possible old NBTTagCompound
+ aDataStick.setTagCompound(new NBTTagCompound());
+ if (GT_Values.D1) {
+ GT_Utility.ItemNBT.setBookTitle(aDataStick, s + " Construction Data ("+aHash+")");
+ }
+ else {
+ GT_Utility.ItemNBT.setBookTitle(aDataStick, s + " Construction Data");
+ }
+
+ NBTTagCompound tNBT = aDataStick.getTagCompound();
+ if (tNBT == null) {
+ tNBT = new NBTTagCompound();
+ }
+
+ tNBT.setTag("output", aNewRecipe.mOutput.writeToNBT(new NBTTagCompound()));
+ tNBT.setInteger("time", aNewRecipe.mDuration);
+ tNBT.setInteger("eu", aNewRecipe.mEUt);
+ for (int i = 0; i < aNewRecipe.mInputs.length; i++) {
+ tNBT.setTag("" + i, aNewRecipe.mInputs[i].writeToNBT(new NBTTagCompound()));
+ }
+ for (int i = 0; i < aNewRecipe.mOreDictAlt.length; i++) {
+ if (aNewRecipe.mOreDictAlt[i] != null && aNewRecipe.mOreDictAlt[i].length > 0) {
+ tNBT.setInteger("a" + i, aNewRecipe.mOreDictAlt[i].length);
+ for (int j = 0; j < aNewRecipe.mOreDictAlt[i].length; j++) {
+ tNBT.setTag("a" + i + ":" + j, aNewRecipe.mOreDictAlt[i][j].writeToNBT(new NBTTagCompound()));
+ }
+ }
+ }
+ for (int i = 0; i < aNewRecipe.mFluidInputs.length; i++) {
+ tNBT.setTag("f" + i, aNewRecipe.mFluidInputs[i].writeToNBT(new NBTTagCompound()));
+ }
+ tNBT.setString("author", "Assembling Line Recipe Generator");
+ NBTTagList tNBTList = new NBTTagList();
+ s = aNewRecipe.mOutput.getDisplayName();
+ if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
+ s = GT_Assemblyline_Server.lServerNames.get(aNewRecipe.mOutput.getDisplayName());
+ if (s == null)
+ s = aNewRecipe.mOutput.getDisplayName();
+ }
+ tNBTList.appendTag(new NBTTagString("Construction plan for " + aNewRecipe.mOutput.stackSize + " " + s + ". Needed EU/t: " + aNewRecipe.mEUt + " Production time: " + (aNewRecipe.mDuration / 20)));
+ for (int i = 0; i < aNewRecipe.mInputs.length; i++) {
+ if (aNewRecipe.mOreDictAlt[i] != null) {
+ int count = 0;
+ StringBuilder tBuilder = new StringBuilder("Input Bus " + (i + 1) + ": ");
+ for (ItemStack tStack : aNewRecipe.mOreDictAlt[i]) {
+ if (tStack != null) {
+ s = tStack.getDisplayName();
+ if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
+ s = GT_Assemblyline_Server.lServerNames.get(tStack.getDisplayName());
+ if (s == null)
+ s = tStack.getDisplayName();
+ }
+
+
+ tBuilder.append(count == 0 ? "" : "\nOr ").append(tStack.stackSize).append(" ").append(s);
+ count++;
+ }
+ }
+ if (count > 0) tNBTList.appendTag(new NBTTagString(tBuilder.toString()));
+ } else if (aNewRecipe.mInputs[i] != null) {
+ s = aNewRecipe.mInputs[i].getDisplayName();
+ if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
+ s = GT_Assemblyline_Server.lServerNames.get(aNewRecipe.mInputs[i].getDisplayName());
+ if (s == null)
+ s = aNewRecipe.mInputs[i].getDisplayName();
+ }
+ tNBTList.appendTag(new NBTTagString("Input Bus " + (i + 1) + ": " + aNewRecipe.mInputs[i].stackSize + " " + s));
+ }
+ }
+ for (int i = 0; i < aNewRecipe.mFluidInputs.length; i++) {
+ if (aNewRecipe.mFluidInputs[i] != null) {
+ s = aNewRecipe.mFluidInputs[i].getLocalizedName();
+ if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
+ s = GT_Assemblyline_Server.lServerNames.get(aNewRecipe.mFluidInputs[i].getLocalizedName());
+ if (s == null)
+ s = aNewRecipe.mFluidInputs[i].getLocalizedName();
+ }
+ tNBTList.appendTag(new NBTTagString("Input Hatch " + (i + 1) + ": " + aNewRecipe.mFluidInputs[i].amount + "L " + s));
+ }
+ }
+ tNBT.setTag("pages", tNBTList);
+ aDataStick.setTagCompound(tNBT);
+ // Set recipe hash
+ setRecipeHashOnDataStick(aDataStick, aHash);
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
new file mode 100644
index 0000000000..bd9148b516
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
@@ -0,0 +1,437 @@
+package gregtech.api.util;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import net.minecraft.world.ChunkCoordIntPair;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.world.WorldEvent;
+import org.apache.commons.io.FileUtils;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A utility to save all kinds of data that is a function of any chunk.
+ * <p>
+ * GregTech takes care of saving and loading the data from disk, and an efficient mechanism to locate it.
+ * Subclass only need to define the exact scheme of each element data (by overriding the three protected abstract method)
+ * <p>
+ * Oh, there is no limit on how large your data is, though you'd not have the familiar NBT interface, but DataOutput
+ * should be reasonably common anyway.
+ * <p>
+ * It should be noted this class is NOT thread safe.
+ * <p>
+ * Element cannot be null.
+ * <p>
+ * TODO: Implement automatic region unloading.
+ *
+ * @param <T> data element type
+ * @author glease
+ */
+@ParametersAreNonnullByDefault
+public abstract class GT_ChunkAssociatedData<T extends GT_ChunkAssociatedData.IData> {
+ private static final Map<String, GT_ChunkAssociatedData<?>> instances = new ConcurrentHashMap<>();
+ private static final int IO_PARALLELISM = Math.min(8, Math.max(1, Runtime.getRuntime().availableProcessors()) * 2 / 3);
+ private static final ExecutorService IO_WORKERS = Executors.newWorkStealingPool(IO_PARALLELISM);
+ private static final Pattern FILE_PATTERN = Pattern.compile("(.+)\\.(-?\\d+)\\.(-?\\d+)\\.dat");
+
+ static {
+ // register event handler
+ new EventHandler();
+ }
+
+ protected final String mId;
+ protected final Class<T> elementtype;
+ private final int regionLength;
+ private final int version;
+ private final boolean saveDefaults;
+ /**
+ * Data is stored as a `(world id -> (super region id -> super region data))` hash map.
+ * where super region's size is determined by regionSize.
+ * Here it is called super region, to not confuse with vanilla's regions.
+ */
+ private final Map<Integer, Map<ChunkCoordIntPair, SuperRegion>> masterMap = new ConcurrentHashMap<>();
+
+ /**
+ * Initialize this instance.
+ *
+ * @param aId An arbitrary, but globally unique identifier for what this data is
+ * @param elementType The class of this element type. Used to create arrays.
+ * @param regionLength The length of one super region. Each super region will contain {@code regionLength * regionLength} chunks
+ * @param version An integer marking the version of this data. Useful later when the data's serialized form changed.
+ */
+ protected GT_ChunkAssociatedData(String aId, Class<T> elementType, int regionLength, byte version, boolean saveDefaults) {
+ if (regionLength * regionLength > Short.MAX_VALUE || regionLength <= 0)
+ throw new IllegalArgumentException("Region invalid: " + regionLength);
+ if (!IData.class.isAssignableFrom(elementType))
+ throw new IllegalArgumentException("Data type invalid");
+ if (aId.contains("."))
+ throw new IllegalArgumentException("ID cannot contains dot");
+ this.mId = aId;
+ this.elementtype = elementType;
+ this.regionLength = regionLength;
+ this.version = version;
+ this.saveDefaults = saveDefaults;
+ if (instances.putIfAbsent(aId, this) != null)
+ throw new IllegalArgumentException("Duplicate GT_ChunkAssociatedData: " + aId);
+ }
+
+ private ChunkCoordIntPair getRegionID(int aChunkX, int aChunkZ) {
+ return new ChunkCoordIntPair(aChunkX / regionLength, aChunkZ / regionLength);
+ }
+
+ /**
+ * Get a reference to data of the chunk that tile entity is in.
+ * The returned reference should be mutable.
+ */
+ public final T get(IGregTechTileEntity tileEntity) {
+ return get(tileEntity.getWorld(), tileEntity.getXCoord() >> 4, tileEntity.getZCoord() >> 4);
+ }
+
+ public final T get(Chunk chunk) {
+ return get(chunk.worldObj, chunk.xPosition, chunk.zPosition);
+ }
+
+ public final T get(World world, ChunkCoordIntPair coord) {
+ return get(world, coord.chunkXPos, coord.chunkZPos);
+ }
+
+ public final T get(World world, int chunkX, int chunkZ) {
+ SuperRegion region = masterMap.computeIfAbsent(world.provider.dimensionId, ignored -> new ConcurrentHashMap<>())
+ .computeIfAbsent(getRegionID(chunkX, chunkZ), c -> new SuperRegion(world, c));
+ return region.get(Math.floorMod(chunkX, regionLength), Math.floorMod(chunkZ, regionLength));
+ }
+
+ protected final void set(World world, int chunkX, int chunkZ, T data) {
+ SuperRegion region = masterMap.computeIfAbsent(world.provider.dimensionId, ignored -> new ConcurrentHashMap<>())
+ .computeIfAbsent(getRegionID(chunkX, chunkZ), c -> new SuperRegion(world, c));
+ region.set(Math.floorMod(chunkX, regionLength), Math.floorMod(chunkZ, regionLength), data);
+ }
+
+ protected final boolean isCreated(int dimId, int chunkX, int chunkZ) {
+ Map<ChunkCoordIntPair, SuperRegion> dimData = masterMap.getOrDefault(dimId, null);
+ if (dimData == null) return false;
+
+ SuperRegion region = dimData.getOrDefault(getRegionID(chunkX, chunkZ), null);
+ if (region == null) return false;
+
+ return region.isCreated(Math.floorMod(chunkX, regionLength), Math.floorMod(chunkZ, regionLength));
+ }
+
+ public void clear() {
+ if (GT_Values.debugWorldData) {
+ long dirtyRegionCount = masterMap.values().stream()
+ .flatMap(m -> m.values().stream())
+ .filter(SuperRegion::isDirty)
+ .count();
+ if (dirtyRegionCount > 0)
+ GT_Log.out.println("Clearing ChunkAssociatedData with " + dirtyRegionCount + " regions dirty. Data might have been lost!");
+ }
+ masterMap.clear();
+ }
+
+ public void save() {
+ saveRegions(masterMap.values().stream().flatMap(m -> m.values().stream()));
+ }
+
+ public void save(World world) {
+ Map<ChunkCoordIntPair, SuperRegion> map = masterMap.get(world.provider.dimensionId);
+ if (map != null)
+ saveRegions(map.values().stream());
+ }
+
+ private void saveRegions(Stream<SuperRegion> stream) {
+ stream.filter(r -> !r.isDirty())
+ .map(c -> (Runnable) c::save)
+ .map(r -> CompletableFuture.runAsync(r, IO_WORKERS))
+ .reduce(CompletableFuture::allOf)
+ .ifPresent(f -> {
+ try {
+ f.get();
+ } catch (Exception e) {
+ GT_Log.err.println("Data save error: " + mId);
+ e.printStackTrace(GT_Log.err);
+ }
+ });
+ }
+
+ protected abstract void writeElement(DataOutput output, T element, World world, int chunkX, int chunkZ) throws IOException;
+
+ protected abstract T readElement(DataInput input, int version, World world, int chunkX, int chunkZ) throws IOException;
+
+ protected abstract T createElement(World world, int chunkX, int chunkZ);
+
+ /**
+ * Clear all mappings, regardless of whether they are dirty
+ */
+ public static void clearAll() {
+ for (GT_ChunkAssociatedData<?> d : instances.values()) d.clear();
+ }
+
+ /**
+ * Save all mappings
+ */
+ public static void saveAll() {
+ for (GT_ChunkAssociatedData<?> d : instances.values()) d.save();
+ }
+
+ /**
+ * Load data for all chunks for a given world.
+ * Current data for that world will be discarded. If this is what you intended, call {@link #save(World)} beforehand.
+ * <p>
+ * Be aware of the memory consumption though.
+ */
+ protected void loadAll(World w) {
+ if (GT_Values.debugWorldData && masterMap.containsKey(w.provider.dimensionId))
+ GT_Log.err.println("Reloading ChunkAssociatedData " + mId + " for world " + w.provider.dimensionId + " discards old data!");
+ if (!getSaveDirectory(w).isDirectory())
+ // nothing to load...
+ return;
+ try {
+ Map<ChunkCoordIntPair, SuperRegion> worldData = Files.list(getSaveDirectory(w).toPath())
+ .map(f -> {
+ Matcher matcher = FILE_PATTERN.matcher(f.getFileName().toString());
+ return matcher.matches() ? matcher : null;
+ })
+ .filter(Objects::nonNull)
+ .filter(m -> mId.equals(m.group(1)))
+ .map(m -> CompletableFuture.supplyAsync(() -> new SuperRegion(w, Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3))), IO_WORKERS))
+ .map(f -> {
+ try {
+ return f.get();
+ } catch (Exception e) {
+ GT_Log.err.println("Error loading region");
+ e.printStackTrace(GT_Log.err);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(SuperRegion::getCoord, Function.identity()));
+ masterMap.put(w.provider.dimensionId, worldData);
+ } catch (IOException e) {
+ GT_Log.err.println("Error loading all region");
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ protected File getSaveDirectory(World w) {
+ return new File(w.getSaveHandler().getWorldDirectory(), "gregtech");
+ }
+
+ public interface IData {
+ /**
+ * @return Whether the data is different from chunk default
+ */
+ boolean isSameAsDefault();
+ }
+
+ protected final class SuperRegion {
+ private final T[] data = createData();
+ private final File backingStorage;
+ private final WeakReference<World> world;
+ private final ChunkCoordIntPair coord;
+
+ private SuperRegion(World world, int chunkX, int chunkZ) {
+ this.world = new WeakReference<>(world);
+ this.coord = new ChunkCoordIntPair(chunkX, chunkZ);
+ backingStorage = new File(getSaveDirectory(world), String.format("%s.%d.%d.dat", mId, chunkX, chunkZ));
+ if (backingStorage.isFile())
+ load();
+ }
+
+ private SuperRegion(World world, ChunkCoordIntPair coord) {
+ this.world = new WeakReference<>(world);
+ this.coord = coord;
+ backingStorage = new File(getSaveDirectory(world), String.format("%s.%d.%d.dat", mId, coord.chunkXPos, coord.chunkZPos));
+ if (backingStorage.isFile())
+ load();
+ }
+
+ @SuppressWarnings("unchecked")
+ private T[] createData() {
+ return (T[]) Array.newInstance(elementtype, regionLength * regionLength);
+ }
+
+ public T get(int subRegionX, int subRegionZ) {
+ int index = getIndex(subRegionX, subRegionZ);
+ T datum = data[index];
+ if (datum == null) {
+ World world = Objects.requireNonNull(this.world.get());
+ T newElem = createElement(world, coord.chunkXPos * regionLength + subRegionX, coord.chunkZPos * regionLength + subRegionZ);
+ data[index] = newElem;
+ return newElem;
+ }
+ return datum;
+ }
+
+ public void set(int subRegionX, int subRegionZ, T data) {
+ this.data[getIndex(subRegionX, subRegionZ)] = data;
+ }
+
+ public boolean isCreated(int subRegionX, int subRegionZ) {
+ return this.data[getIndex(subRegionX, subRegionZ)] == null;
+ }
+
+ public ChunkCoordIntPair getCoord() {
+ return coord;
+ }
+
+ private int getIndex(int subRegionX, int subRegionY) {
+ return subRegionX * regionLength + subRegionY;
+ }
+
+ private int getChunkX(int index) {
+ return index / regionLength + coord.chunkXPos;
+ }
+
+ private int getChunkZ(int index) {
+ return index % regionLength + coord.chunkZPos;
+ }
+
+ public boolean isDirty() {
+ for (T datum : data) {
+ if (datum != null && datum.isSameAsDefault())
+ return true;
+ }
+ return false;
+ }
+
+ public void save() {
+ try {
+ save0();
+ } catch (IOException e) {
+ GT_Log.err.println("Error saving data " + backingStorage.getPath());
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private void save0() throws IOException {
+ if (!isDirty())
+ return;
+ //noinspection ResultOfMethodCallIgnored
+ backingStorage.getParentFile().mkdirs();
+ File tmpFile = getTmpFile();
+ World world = Objects.requireNonNull(this.world.get(), "Attempting to save region of another world!");
+ try (DataOutputStream output = new DataOutputStream(new FileOutputStream(tmpFile))) {
+ int ptr = 0;
+ boolean nullRange = data[0] == null;
+ // write a magic byte as storage format version
+ output.writeByte(0);
+ // write a magic byte as data format version
+ output.writeByte(version);
+ output.writeBoolean(nullRange);
+ while (ptr < data.length) {
+ // work out how long is this range
+ int rangeStart = ptr;
+ while (ptr < data.length && (data[ptr] == null || (!saveDefaults && data[ptr].isSameAsDefault())) == nullRange)
+ ptr++;
+ // write range length
+ output.writeShort(ptr - rangeStart);
+ if (!nullRange)
+ // write element data
+ for (int i = rangeStart; i < ptr; i++)
+ writeElement(output, data[i], world, getChunkX(ptr), getChunkZ(ptr));
+ // or not
+ nullRange = !nullRange;
+ }
+ }
+ // first try to replace the destination file
+ // since atomic operation, no need to keep the backup in place
+ try {
+ Files.move(tmpFile.toPath(), backingStorage.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ } catch (AtomicMoveNotSupportedException ignored) {
+ // in case some dumb system/jre combination would cause this
+ // or if **somehow** two file inside the same directory belongs two separate filesystem
+ FileUtils.copyFile(tmpFile, backingStorage);
+ }
+ }
+
+ public void load() {
+ try {
+ loadFromFile(backingStorage);
+ } catch (IOException | RuntimeException e) {
+ GT_Log.err.println("Primary storage file broken in " + backingStorage.getPath());
+ e.printStackTrace(GT_Log.err);
+ // in case the primary storage is broken
+ try {
+ loadFromFile(getTmpFile());
+ } catch (IOException | RuntimeException e2) {
+ GT_Log.err.println("Backup storage file broken in " + backingStorage.getPath());
+ e2.printStackTrace(GT_Log.err);
+ }
+ }
+ }
+
+ private void loadFromFile(File file) throws IOException {
+ World world = Objects.requireNonNull(this.world.get(), "Attempting to load region of another world!");
+ try (DataInputStream input = new DataInputStream(new FileInputStream(file))) {
+ boolean nullRange = input.readBoolean();
+ int ptr = 0;
+ while (ptr != data.length) {
+ int rangeEnd = ptr + input.readUnsignedShort();
+ if (!nullRange) {
+ for (; ptr < rangeEnd; ptr++) {
+ data[ptr] = readElement(input, version, world, getChunkX(ptr), getChunkZ(ptr));
+ }
+ } else {
+ Arrays.fill(data, ptr, rangeEnd, null);
+ ptr = rangeEnd;
+ }
+ }
+ }
+ }
+
+ private File getTmpFile() {
+ return new File(backingStorage.getParentFile(), backingStorage.getName() + ".tmp");
+ }
+ }
+
+ public static class EventHandler {
+ private EventHandler() {
+ MinecraftForge.EVENT_BUS.register(this);
+ }
+
+ @SubscribeEvent
+ public void onWorldSave(WorldEvent.Save e) {
+ for (GT_ChunkAssociatedData<?> d : instances.values()) {
+ d.save(e.world);
+ }
+ }
+
+ @SubscribeEvent
+ public void onWorldUnload(WorldEvent.Unload e) {
+ for (GT_ChunkAssociatedData<?> d : instances.values()) {
+ // there is no need to explicitly do a save here
+ // forge will send a WorldEvent.Save on server thread before this event is distributed
+ d.masterMap.remove(e.world.provider.dimensionId);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_CoverBehavior.java b/src/main/java/gregtech/api/util/GT_CoverBehavior.java
index 14c8cc1308..9972331eb3 100644
--- a/src/main/java/gregtech/api/util/GT_CoverBehavior.java
+++ b/src/main/java/gregtech/api/util/GT_CoverBehavior.java
@@ -3,21 +3,179 @@ package gregtech.api.util;
import gregtech.api.enums.GT_Values;
import gregtech.api.interfaces.tileentity.ICoverable;
import gregtech.api.net.GT_Packet_TileEntityCoverGUI;
-import gregtech.api.objects.GT_ItemStack;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
import net.minecraftforge.fluids.Fluid;
import static gregtech.api.enums.GT_Values.E;
/**
- * For Covers with a special behavior.
+ * For Covers with a special behavior. Has fixed storage format of 4 byte. Not very convenient...
*/
-public abstract class GT_CoverBehavior {
+public abstract class GT_CoverBehavior extends GT_CoverBehaviorBase<ISerializableObject.LegacyCoverData> {
public EntityPlayer lastPlayer = null;
+ public GT_CoverBehavior() {
+ super(ISerializableObject.LegacyCoverData.class);
+ }
+
+ private static int convert(ISerializableObject.LegacyCoverData data) {
+ return data == null ? 0 : data.get();
+ }
+
+ // region bridge the parent call to legacy calls
+
+ @Override
+ public final ISerializableObject.LegacyCoverData createDataObject() {
+ return new ISerializableObject.LegacyCoverData();
+ }
+
+ @Override
+ public ISerializableObject.LegacyCoverData createDataObject(int aLegacyData) {
+ return new ISerializableObject.LegacyCoverData(aLegacyData);
+ }
+
+ @Override
+ protected boolean isRedstoneSensitiveImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return isRedstoneSensitive(aSide, aCoverID, aCoverVariable.get(), aTileEntity, aTimer);
+ }
+
+ @Override
+ protected ISerializableObject.LegacyCoverData doCoverThingsImpl(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ if (aCoverVariable == null)
+ aCoverVariable = new ISerializableObject.LegacyCoverData();
+ aCoverVariable.set(doCoverThings(aSide, aInputRedstone, aCoverID, aCoverVariable.get(), aTileEntity, aTimer));
+ return aCoverVariable;
+ }
+
+ @Override
+ protected boolean onCoverRightClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return onCoverRightclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ);
+ }
+
+ @Override
+ protected ISerializableObject.LegacyCoverData onCoverScrewdriverClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ if (aCoverVariable == null)
+ aCoverVariable = new ISerializableObject.LegacyCoverData();
+ aCoverVariable.set(onCoverScrewdriverclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ));
+ return aCoverVariable;
+ }
+
+ @Override
+ protected boolean onCoverShiftRightClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) {
+ return onCoverShiftRightclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer);
+ }
+
+ @Override
+ protected Object getClientGUIImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) {
+ return getClientGUI(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean onCoverRemovalImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, boolean aForced) {
+ return onCoverRemoval(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aForced);
+ }
+
+ @Override
+ protected String getDescriptionImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getDescription(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected float getBlastProofLevelImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getBlastProofLevel(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsRedstoneGoInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsRedstoneGoOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFibreGoInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsFibreGoIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFibreGoOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsFibreGoOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsEnergyInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsEnergyOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFluidInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidIn(aSide, aCoverID, convert(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFluidOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidOut(aSide, aCoverID, convert(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsItemsInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsIn(aSide, aCoverID, convert(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsItemsOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsOut(aSide, aCoverID, convert(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ @Override
+ protected boolean isGUIClickableImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return isGUIClickable(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean manipulatesSidedRedstoneOutputImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return manipulatesSidedRedstoneOutput(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean alwaysLookConnectedImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return alwaysLookConnected(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected byte getRedstoneInputImpl(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getRedstoneInput(aSide, aInputRedstone, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected int getTickRateImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getTickRate(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected byte getLensColorImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getLensColor(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected ItemStack getDropImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getDrop(aSide, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ // endregion
+
public boolean isRedstoneSensitive(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity, long aTimer) {
return true;
}
@@ -39,15 +197,6 @@ public abstract class GT_CoverBehavior {
}
/**
- * Called when someone rightclicks this Cover Client Side
- * <p/>
- * return true, if something actually happens.
- */
- public boolean onCoverRightclickClient(byte aSide, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
- return false;
- }
-
- /**
* Called when someone rightclicks this Cover with a Screwdriver. Doesn't call @onCoverRightclick in this Case.
* <p/>
* return the new Value of the Cover Variable
@@ -68,22 +217,11 @@ public abstract class GT_CoverBehavior {
return false;
}
- public boolean hasCoverGUI() {
- return false;
- }
-
public Object getClientGUI(byte aSide, int aCoverID, int coverData, ICoverable aTileEntity) {
return null;
}
/**
- * Checks if the Cover can be placed on this.
- */
- public boolean isCoverPlaceable(byte aSide, GT_ItemStack aStack, ICoverable aTileEntity) {
- return true;
- }
-
- /**
* Removes the Cover if this returns true, or if aForced is true.
* Doesn't get called when the Machine Block is getting broken, only if you break the Cover away from the Machine.
*/
@@ -219,13 +357,6 @@ public abstract class GT_CoverBehavior {
}
/**
- * If this is a simple Cover, which can also be used on Bronze Machines and similar.
- */
- public boolean isSimpleCover() {
- return false;
- }
-
- /**
* The MC Color of this Lens. -1 for no Color (meaning this isn't a Lens then).
*/
public byte getLensColor(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
@@ -238,15 +369,4 @@ public abstract class GT_CoverBehavior {
public ItemStack getDrop(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
return GT_OreDictUnificator.get(true, aTileEntity.getCoverItemAtSide(aSide));
}
-
- /**
- * @return sets the Cover upon placement.
- */
- public void placeCover(byte aSide, ItemStack aCover, ICoverable aTileEntity) {
- aTileEntity.setCoverIDAtSide(aSide, GT_Utility.stackToInt(aCover));
- }
-
- public String trans(String aNr, String aEnglish){
- return GT_LanguageManager.addStringLocalization("Interaction_DESCRIPTION_Index_"+aNr, aEnglish, false);
- }
}
diff --git a/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java
new file mode 100644
index 0000000000..50ef2632d9
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java
@@ -0,0 +1,482 @@
+package gregtech.api.util;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.net.GT_Packet_TileEntityCoverGUI;
+import gregtech.api.objects.GT_ItemStack;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.world.World;
+import net.minecraftforge.fluids.Fluid;
+
+import static gregtech.api.enums.GT_Values.E;
+
+/**
+ * For Covers with a special behavior.
+ *
+ * @author glease
+ */
+public abstract class GT_CoverBehaviorBase<T extends ISerializableObject> {
+
+ public EntityPlayer lastPlayer = null;
+ private final Class<T> typeToken;
+
+ protected GT_CoverBehaviorBase(Class<T> typeToken) {
+ this.typeToken = typeToken;
+ }
+
+ public abstract T createDataObject(int aLegacyData);
+
+ public abstract T createDataObject();
+
+ public T createDataObject(NBTBase aNBT) {
+ T ret = createDataObject();
+ ret.loadDataFromNBT(aNBT);
+ return ret;
+ }
+
+ public final T cast(ISerializableObject aData) {
+ if (typeToken.isInstance(aData))
+ return forceCast(aData);
+ return null;
+ }
+
+ private T forceCast(ISerializableObject aData) {
+ return typeToken.cast(aData);
+ }
+
+ // region facade
+
+ public final boolean isRedstoneSensitive(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return isRedstoneSensitiveImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer);
+ }
+
+ /**
+ * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time.
+ */
+ public final T doCoverThings(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return doCoverThingsImpl(aSide, aInputRedstone, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer);
+ }
+
+ /**
+ * Called when someone rightclicks this Cover.
+ * <p/>
+ * return true, if something actually happens.
+ */
+ public final boolean onCoverRightClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return onCoverRightClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ);
+ }
+
+ /**
+ * Called when someone rightclicks this Cover with a Screwdriver. Doesn't call @onCoverRightclick in this Case.
+ * <p/>
+ * return the new Value of the Cover Variable
+ */
+ public final T onCoverScrewdriverClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return onCoverScrewdriverClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ);
+ }
+
+ /**
+ * Called when someone shift-rightclicks this Cover with no tool. Doesn't call @onCoverRightclick in this Case.
+ */
+ public final boolean onCoverShiftRightClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) {
+ return onCoverShiftRightClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer);
+ }
+
+ public final Object getClientGUI(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) {
+ return getClientGUIImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aWorld);
+ }
+
+ /**
+ * Removes the Cover if this returns true, or if aForced is true.
+ * Doesn't get called when the Machine Block is getting broken, only if you break the Cover away from the Machine.
+ */
+ public final boolean onCoverRemoval(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, boolean aForced) {
+ return onCoverRemovalImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aForced);
+ }
+
+ /**
+ * Gives a small Text for the status of the Cover.
+ */
+ public final String getDescription(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getDescriptionImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * How Blast Proof the Cover is. 30 is normal.
+ */
+ public final float getBlastProofLevel(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getBlastProofLevelImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets RS-Signals into the Block
+ * <p/>
+ * This is just Informative so that Machines know if their Redstone Input is blocked or not
+ */
+ public final boolean letsRedstoneGoIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets RS-Signals out of the Block
+ */
+ public final boolean letsRedstoneGoOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Fibre-Signals into the Block
+ * <p/>
+ * This is just Informative so that Machines know if their Redstone Input is blocked or not
+ */
+ public final boolean letsFibreGoIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsFibreGoInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Fibre-Signals out of the Block
+ */
+ public final boolean letsFibreGoOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsFibreGoOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Energy into the Block
+ */
+ public final boolean letsEnergyIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Energy out of the Block
+ */
+ public final boolean letsEnergyOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Liquids into the Block, aFluid can be null meaning if this is generally allowing Fluids or not.
+ */
+ public final boolean letsFluidIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidInImpl(aSide, aCoverID, forceCast(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ /**
+ * If it lets Liquids out of the Block, aFluid can be null meaning if this is generally allowing Fluids or not.
+ */
+ public final boolean letsFluidOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ /**
+ * If it lets Items into the Block, aSlot = -1 means if it is generally accepting Items (return false for no eraction at all), aSlot = -2 means if it would accept for all Slots Impl(return true to skip the Checks for each Slot).
+ */
+ public final boolean letsItemsIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsInImpl(aSide, aCoverID, forceCast(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ /**
+ * If it lets Items out of the Block, aSlot = -1 means if it is generally accepting Items (return false for no eraction at all), aSlot = -2 means if it would accept for all Slots Impl(return true to skip the Checks for each Slot).
+ */
+ public final boolean letsItemsOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ /**
+ * If it lets you rightclick the Machine normally
+ */
+ public final boolean isGUIClickable(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return isGUIClickableImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Needs to return true for Covers, which have a Redstone Output on their Facing.
+ */
+ public final boolean manipulatesSidedRedstoneOutput(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return manipulatesSidedRedstoneOutputImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * if this Cover should let Pipe Connections look connected even if it is not the case.
+ */
+ public final boolean alwaysLookConnected(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return alwaysLookConnectedImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Called to determine the incoming Redstone Signal of a Machine.
+ * Returns the original Redstone per default.
+ * The Cover should @letsRedstoneGoIn or the aInputRedstone Parameter is always 0.
+ */
+ public final byte getRedstoneInput(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getRedstoneInputImpl(aSide, aInputRedstone, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Gets the Tick Rate for doCoverThings of the Cover
+ * <p/>
+ * 0 = No Ticks! Yes, 0 is Default, you have to override this
+ */
+ public final int getTickRate(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getTickRateImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+
+ /**
+ * The MC Color of this Lens. -1 for no Color (meaning this isn't a Lens then).
+ */
+ public final byte getLensColor(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getLensColorImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * @return the ItemStack dropped by this Cover
+ */
+ public final ItemStack getDrop(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getDropImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+ // endregion
+
+ // region impl
+
+ protected boolean isRedstoneSensitiveImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return true;
+ }
+
+ /**
+ * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time.
+ */
+ protected T doCoverThingsImpl(byte aSide, byte aInputRedstone, int aCoverID, T aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return aCoverVariable;
+ }
+
+ /**
+ * Called when someone rightclicks this Cover.
+ * <p/>
+ * return true, if something actually happens.
+ */
+ protected boolean onCoverRightClickImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return false;
+ }
+
+ /**
+ * Called when someone rightclicks this Cover with a Screwdriver. Doesn't call @onCoverRightclick in this Case.
+ * <p/>
+ * return the new Value of the Cover Variable
+ */
+ protected T onCoverScrewdriverClickImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return aCoverVariable;
+ }
+
+ /**
+ * Called when someone shift-rightclicks this Cover with no tool. Doesn't call @onCoverRightclick in this Case.
+ */
+ protected boolean onCoverShiftRightClickImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) {
+ if (hasCoverGUI() && aPlayer instanceof EntityPlayerMP) {
+ lastPlayer = aPlayer;
+ GT_Values.NW.sendToPlayer(new GT_Packet_TileEntityCoverGUI(aSide, aCoverID, aCoverVariable, aTileEntity, (EntityPlayerMP) aPlayer), (EntityPlayerMP) aPlayer);
+ return true;
+ }
+ return false;
+ }
+
+ protected Object getClientGUIImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) {
+ return null;
+ }
+
+ /**
+ * Removes the Cover if this returns true, or if aForced is true.
+ * Doesn't get called when the Machine Block is getting broken, only if you break the Cover away from the Machine.
+ */
+ protected boolean onCoverRemovalImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity, boolean aForced) {
+ return true;
+ }
+
+ /**
+ * Gives a small Text for the status of the Cover.
+ */
+ protected String getDescriptionImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return E;
+ }
+
+ /**
+ * How Blast Proof the Cover is. 30 is normal.
+ */
+ protected float getBlastProofLevelImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return 10.0F;
+ }
+
+ /**
+ * If it lets RS-Signals into the Block
+ * <p/>
+ * This is just Informative so that Machines know if their Redstone Input is blocked or not
+ */
+ protected boolean letsRedstoneGoInImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets RS-Signals out of the Block
+ */
+ protected boolean letsRedstoneGoOutImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Fibre-Signals into the Block
+ * <p/>
+ * This is just Informative so that Machines know if their Redstone Input is blocked or not
+ */
+ protected boolean letsFibreGoInImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Fibre-Signals out of the Block
+ */
+ protected boolean letsFibreGoOutImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy into the Block
+ */
+ protected boolean letsEnergyInImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy out of the Block
+ */
+ protected boolean letsEnergyOutImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Liquids into the Block, aFluid can be null meaning if this is generally allowing Fluids or not.
+ */
+ protected boolean letsFluidInImpl(byte aSide, int aCoverID, T aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Liquids out of the Block, aFluid can be null meaning if this is generally allowing Fluids or not.
+ */
+ protected boolean letsFluidOutImpl(byte aSide, int aCoverID, T aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Items into the Block, aSlot = -1 means if it is generally accepting Items (return false for no Interaction at all), aSlot = -2 means if it would accept for all Slots (return true to skip the Checks for each Slot).
+ */
+ protected boolean letsItemsInImpl(byte aSide, int aCoverID, T aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Items out of the Block, aSlot = -1 means if it is generally accepting Items (return false for no Interaction at all), aSlot = -2 means if it would accept for all Slots (return true to skip the Checks for each Slot).
+ */
+ protected boolean letsItemsOutImpl(byte aSide, int aCoverID, T aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets you rightclick the Machine normally
+ */
+ protected boolean isGUIClickableImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * Needs to return true for Covers, which have a Redstone Output on their Facing.
+ */
+ protected boolean manipulatesSidedRedstoneOutputImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * if this Cover should let Pipe Connections look connected even if it is not the case.
+ */
+ protected boolean alwaysLookConnectedImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * Called to determine the incoming Redstone Signal of a Machine.
+ * Returns the original Redstone per default.
+ * The Cover should @letsRedstoneGoIn or the aInputRedstone Parameter is always 0.
+ */
+ protected byte getRedstoneInputImpl(byte aSide, byte aInputRedstone, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoIn(aSide, aCoverID, aCoverVariable, aTileEntity) ? aInputRedstone : 0;
+ }
+
+ /**
+ * Gets the Tick Rate for doCoverThings of the Cover
+ * <p/>
+ * 0 = No Ticks! Yes, 0 is Default, you have to override this
+ */
+ protected int getTickRateImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return 0;
+ }
+
+
+ /**
+ * The MC Color of this Lens. -1 for no Color (meaning this isn't a Lens then).
+ */
+ protected byte getLensColorImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return -1;
+ }
+
+ /**
+ * @return the ItemStack dropped by this Cover
+ */
+ protected ItemStack getDropImpl(byte aSide, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return GT_OreDictUnificator.get(true, aTileEntity.getCoverItemAtSide(aSide));
+ }
+
+ //endregion
+
+ // region no data
+
+ /**
+ * Checks if the Cover can be placed on this.
+ */
+ public boolean isCoverPlaceable(byte aSide, GT_ItemStack aStack, ICoverable aTileEntity) {
+ return true;
+ }
+
+ public boolean hasCoverGUI() {
+ return false;
+ }
+
+ /**
+ * Called when someone rightclicks this Cover Client Side
+ * <p/>
+ * return true, if something actually happens.
+ */
+ public boolean onCoverRightclickClient(byte aSide, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return false;
+ }
+
+ /**
+ * If this is a simple Cover, which can also be used on Bronze Machines and similar.
+ */
+ public boolean isSimpleCover() {
+ return false;
+ }
+
+ /**
+ * sets the Cover upon placement.
+ */
+ public void placeCover(byte aSide, ItemStack aCover, ICoverable aTileEntity) {
+ aTileEntity.setCoverIDAtSide(aSide, GT_Utility.stackToInt(aCover));
+ }
+
+ public String trans(String aNr, String aEnglish) {
+ return GT_LanguageManager.addStringLocalization("Interaction_DESCRIPTION_Index_" + aNr, aEnglish, false);
+ }
+ // endregion
+}
diff --git a/src/main/java/gregtech/api/util/GT_LanguageManager.java b/src/main/java/gregtech/api/util/GT_LanguageManager.java
index e0c23fce8e..767c830f79 100644
--- a/src/main/java/gregtech/api/util/GT_LanguageManager.java
+++ b/src/main/java/gregtech/api/util/GT_LanguageManager.java
@@ -16,6 +16,7 @@ import static gregtech.api.enums.GT_Values.E;
public class GT_LanguageManager {
public static final HashMap<String, String> TEMPMAP = new HashMap<String, String>(), BUFFERMAP = new HashMap<String, String>(), LANGMAP = new HashMap<String, String>();
public static Configuration sEnglishFile;
+ public static String sLanguage = "en_US";
public static boolean sUseEnglishFile = false;
public static boolean i18nPlaceholder = true;
@@ -31,7 +32,7 @@ public class GT_LanguageManager {
}
}
TEMPMAP.put(aKey.trim(), aEnglish);
- LanguageRegistry.instance().injectLanguage("en_US", TEMPMAP);
+ LanguageRegistry.instance().injectLanguage(sLanguage, TEMPMAP);
TEMPMAP.clear();
if(sUseEnglishFile && !aWriteIntoLangFile){
if (!LANGMAP.containsKey(aKey)) {
@@ -334,6 +335,8 @@ public class GT_LanguageManager {
addStringLocalization("Interaction_DESCRIPTION_Index_216", "Deprecated Recipe");
addStringLocalization("Interaction_DESCRIPTION_Index_217", "Stocking mode. Keeps this many items in destination input slots. This mode can be server unfriendly.");
addStringLocalization("Interaction_DESCRIPTION_Index_218", "Transfer size mode. Add exactly this many items in destination input slots as long as there is room.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_219", "Single recipe locking enabled. Will lock to next recipe.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_220", "Single recipe locking disabled.");
addStringLocalization("Interaction_DESCRIPTION_Index_500", "Fitting: Loose - More Flow");
addStringLocalization("Interaction_DESCRIPTION_Index_501", "Fitting: Tight - More Efficiency");
diff --git a/src/main/java/gregtech/api/util/GT_ModHandler.java b/src/main/java/gregtech/api/util/GT_ModHandler.java
index a910723069..810a204c40 100644
--- a/src/main/java/gregtech/api/util/GT_ModHandler.java
+++ b/src/main/java/gregtech/api/util/GT_ModHandler.java
@@ -91,7 +91,7 @@ public class GT_ModHandler {
public static volatile int VERSION = 509;
public static Collection<String> sNativeRecipeClasses = new HashSet<>(), sSpecialRecipeClasses = new HashSet<>();
public static GT_HashSet<GT_ItemStack> sNonReplaceableItems = new GT_HashSet<>();
- public static Object sBoxableWrapper = GT_Utility.callConstructor("gregtechmod.api.util.GT_IBoxableWrapper", 0, null, false);
+ public static Object sBoxableWrapper = new GT_IBoxableWrapper();
private static final Map<IRecipeInput, RecipeOutput> sExtractorRecipes = new HashMap<>();
private static final Map<IRecipeInput, RecipeOutput> sMaceratorRecipes = new HashMap<>();
private static final Map<IRecipeInput, RecipeOutput> sCompressorRecipes = new HashMap<>();
diff --git a/src/main/java/gregtech/api/util/GT_OreDictUnificator.java b/src/main/java/gregtech/api/util/GT_OreDictUnificator.java
index 54ef5b2866..a017cf3bb0 100644
--- a/src/main/java/gregtech/api/util/GT_OreDictUnificator.java
+++ b/src/main/java/gregtech/api/util/GT_OreDictUnificator.java
@@ -223,26 +223,30 @@ public class GT_OreDictUnificator {
rStack = tPrefixMaterial.mUnificationTarget;
if (GT_Utility.isStackInvalid(rStack))
return !alreadyCompared && GT_Utility.areStacksEqual(aStack, unified_tStack, true);
- assert rStack != null;
rStack.setTagCompound(aStack.getTagCompound());
return GT_Utility.areStacksEqual(rStack, unified_tStack, true);
}
public static List<ItemStack> getNonUnifiedStacks(Object obj) {
- synchronized (sUnificationTable) {
- if (sUnificationTable.isEmpty() && !sItemStack2DataMap.isEmpty()) {
- for (GT_ItemStack tGTStack0 : sItemStack2DataMap.keySet()) {
- ItemStack tStack0 = tGTStack0.toStack();
- ItemStack tStack1 = get(false, tStack0);
- if (!GT_Utility.areStacksEqual(tStack0, tStack1)) {
- GT_ItemStack tGTStack1 = new GT_ItemStack(tStack1);
- List<ItemStack> list = sUnificationTable.computeIfAbsent(tGTStack1, k -> new ArrayList<>());
- if (!list.contains(tStack0))
- list.add(tStack0);
- }
- }
- }
- }
+ if (sUnificationTable.isEmpty() && !sItemStack2DataMap.isEmpty()) {
+ // use something akin to double check lock. this synchronization overhead is causing lag whenever my
+ // 5900x tries to do NEI lookup
+ synchronized (sUnificationTable) {
+ if (sUnificationTable.isEmpty() && !sItemStack2DataMap.isEmpty()) {
+ for (GT_ItemStack tGTStack0 : sItemStack2DataMap.keySet()) {
+ ItemStack tStack0 = tGTStack0.toStack();
+ ItemStack tStack1 = get_nocopy(false, tStack0);
+ if (!GT_Utility.areStacksEqual(tStack0, tStack1)) {
+ GT_ItemStack tGTStack1 = new GT_ItemStack(tStack1);
+ List<ItemStack> list = sUnificationTable.computeIfAbsent(tGTStack1, k -> new ArrayList<>());
+ // greg's original code tries to dedupe the list using List#contains, which won't work
+ // on vanilla ItemStack. I removed it since it never worked and can be slow.
+ list.add(tStack0);
+ }
+ }
+ }
+ }
+ }
ItemStack[] aStacks = {};
if (obj instanceof ItemStack)
aStacks = new ItemStack[]{(ItemStack) obj};
@@ -257,7 +261,6 @@ public class GT_OreDictUnificator {
if (tList != null) {
for (ItemStack tStack : tList) {
ItemStack tStack1 = GT_Utility.copyAmount(aStack.stackSize, tStack);
- tStack1.setTagCompound(aStack.getTagCompound());
rList.add(tStack1);
}
}
@@ -315,7 +318,7 @@ public class GT_OreDictUnificator {
public static ItemData getItemData(ItemStack aStack) {
if (GT_Utility.isStackInvalid(aStack)) return null;
ItemData rData = sItemStack2DataMap.get(new GT_ItemStack(aStack));
- if (rData == null) rData = sItemStack2DataMap.get(new GT_ItemStack(GT_Utility.copyMetaData(W, aStack)));
+ if (rData == null) rData = sItemStack2DataMap.get(new GT_ItemStack(aStack, true));
return rData;
}
diff --git a/src/main/java/gregtech/api/util/GT_Recipe.java b/src/main/java/gregtech/api/util/GT_Recipe.java
index f941a86f5c..7b74f95a6b 100644
--- a/src/main/java/gregtech/api/util/GT_Recipe.java
+++ b/src/main/java/gregtech/api/util/GT_Recipe.java
@@ -302,6 +302,13 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
}
}
+ public GT_Recipe(FluidStack aInput1, FluidStack aOutput1, int aDuration, int aEUt) {
+ this(false, null, null, null, null, new FluidStack[]{aInput1}, new FluidStack[]{aOutput1}, Math.max(aDuration, 1), aEUt, 0);
+ if (mFluidInputs.length > 0 && mFluidOutputs[0] != null) {
+ GT_Recipe_Map.sVacuumRecipes.addRecipe(this);
+ }
+ }
+
//Dummy GT_Recipe maker...
public GT_Recipe(ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems, int[] aChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue){
this(true, aInputs, aOutputs, aSpecialItems, aChances, aFluidInputs, aFluidOutputs, aDuration, aEUt, aSpecialValue);
@@ -557,6 +564,84 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
mEUt = aEUt;
mOreDictAlt = aAlt;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ GT_ItemStack[] thisInputs = new GT_ItemStack[this.mInputs.length];
+ int totalInputStackSize = 0;
+ for (int i=0;i<this.mInputs.length;i++) {
+ thisInputs[i] = new GT_ItemStack(this.mInputs[i]);
+ totalInputStackSize += thisInputs[i].mStackSize;
+ }
+ int inputHash = Arrays.deepHashCode(thisInputs);
+ int inputFluidHash = Arrays.deepHashCode(this.mFluidInputs);
+ GT_ItemStack thisOutput = new GT_ItemStack(mOutput);
+ GT_ItemStack thisResearch = new GT_ItemStack(mResearchItem);
+ int miscRecipeDataHash = Arrays.deepHashCode(new Object[] {
+ totalInputStackSize,
+ mDuration, mEUt,
+ thisOutput,
+ thisResearch,
+ mResearchTime
+ });
+ result = prime * result + inputFluidHash;
+ result = prime * result + inputHash;
+ result = prime * result + miscRecipeDataHash;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof GT_Recipe_AssemblyLine)) {
+ return false;
+ }
+ GT_Recipe_AssemblyLine other = (GT_Recipe_AssemblyLine) obj;
+ if (this.mInputs.length != other.mInputs.length) {
+ return false;
+ }
+ if (this.mFluidInputs.length != other.mFluidInputs.length) {
+ return false;
+ }
+ // Check Outputs Match
+ GT_ItemStack output1 = new GT_ItemStack(this.mOutput);
+ GT_ItemStack output2 = new GT_ItemStack(other.mOutput);
+ if (!output1.equals(output2)) {
+ return false;
+ }
+ // Check Scanned Item Match
+ GT_ItemStack scan1 = new GT_ItemStack(this.mResearchItem);
+ GT_ItemStack scan2 = new GT_ItemStack(other.mResearchItem);
+ if (!scan1.equals(scan2)) {
+ return false;
+ }
+ // Check Items Match
+ GT_ItemStack[] thisInputs = new GT_ItemStack[this.mInputs.length];
+ GT_ItemStack[] otherInputs = new GT_ItemStack[other.mInputs.length];
+ for (int i=0;i<thisInputs.length;i++) {
+ thisInputs[i] = new GT_ItemStack(this.mInputs[i]);
+ otherInputs[i] = new GT_ItemStack(other.mInputs[i]);
+ }
+ for (int i=0;i<thisInputs.length;i++) {
+ if (!thisInputs[i].equals(otherInputs[i]) || thisInputs[i].mStackSize != otherInputs[i].mStackSize) {
+ return false;
+ }
+ }
+ // Check Fluids Match
+ for (int i=0;i<this.mFluidInputs.length;i++) {
+ if (!this.mFluidInputs[i].isFluidStackIdentical(other.mFluidInputs[i])) {
+ return false;
+ }
+ }
+
+ return this.mDuration == other.mDuration
+ && this.mEUt == other.mEUt
+ && this.mResearchTime == other.mResearchTime;
+ }
}
@@ -565,6 +650,10 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
* Contains all Recipe Maps
*/
public static final Collection<GT_Recipe_Map> sMappings = new ArrayList<>();
+ /**
+ * All recipe maps indexed by their {@link #mUniqueIdentifier}.
+ */
+ public static final Map<String, GT_Recipe_Map> sIndexedMappings = new HashMap<>();
public static final GT_Recipe_Map sOreWasherRecipes = new GT_Recipe_Map(new HashSet<>(500), "gt.recipe.orewasher", "Ore Washing Plant", null, RES_PATH_GUI + "basicmachines/OreWasher", 1, 3, 1, 1, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sThermalCentrifugeRecipes = new GT_Recipe_Map(new HashSet<>(1000), "gt.recipe.thermalcentrifuge", "Thermal Centrifuge", null, RES_PATH_GUI + "basicmachines/ThermalCentrifuge", 1, 3, 1, 0, 2, E, 1, E, true, true);
@@ -588,8 +677,8 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
public static final GT_Recipe_Map sSifterRecipes = new GT_Recipe_Map(new HashSet<>(105), "gt.recipe.sifter", "Sifter", null, RES_PATH_GUI + "basicmachines/Sifter", 1, 9, 1, 0, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sPressRecipes = new GT_Recipe_Map_FormingPress(new HashSet<>(300), "gt.recipe.press", "Forming Press", null, RES_PATH_GUI + "basicmachines/Press", 2, 1, 2, 0, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sLaserEngraverRecipes = new GT_Recipe_Map(new HashSet<>(810), "gt.recipe.laserengraver", "Precision Laser Engraver", null, RES_PATH_GUI + "basicmachines/LaserEngraver", 2, 1, 2, 0, 1, E, 1, E, true, true);
- public static final GT_Recipe_Map sMixerRecipes = new GT_Recipe_Map(new HashSet<>(900), "gt.recipe.mixer", "Mixer", null, RES_PATH_GUI + "basicmachines/Mixer2", 9, 1, 1, 0, 1, E, 1, E, true, true);
- public static final GT_Recipe_Map sAutoclaveRecipes = new GT_Recipe_Map(new HashSet<>(300), "gt.recipe.autoclave", "Autoclave", null, RES_PATH_GUI + "basicmachines/Autoclave", 2, 1, 1, 1, 1, E, 1, E, true, true);
+ public static final GT_Recipe_Map sMixerRecipes = new GT_Recipe_Map(new HashSet<>(900), "gt.recipe.mixer", "Mixer", null, RES_PATH_GUI + "basicmachines/Mixer6", 9, 4, 1, 0, 1, E, 1, E, true, true);
+ public static final GT_Recipe_Map sAutoclaveRecipes = new GT_Recipe_Map(new HashSet<>(300), "gt.recipe.autoclave", "Autoclave", null, RES_PATH_GUI + "basicmachines/Autoclave4", 2, 4, 1, 1, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sElectroMagneticSeparatorRecipes = new GT_Recipe_Map(new HashSet<>(50), "gt.recipe.electromagneticseparator", "Electromagnetic Separator", null, RES_PATH_GUI + "basicmachines/ElectromagneticSeparator", 1, 3, 1, 0, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sPolarizerRecipes = new GT_Recipe_Map(new HashSet<>(300), "gt.recipe.polarizer", "Electromagnetic Polarizer", null, RES_PATH_GUI + "basicmachines/Polarizer", 1, 1, 1, 0, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sMaceratorRecipes = new GT_Recipe_Map_Macerator(new HashSet<>(16600), "gt.recipe.macerator", "Pulverization", null, RES_PATH_GUI + "basicmachines/Macerator4", 1, 4, 1, 0, 1, E, 1, E, true, true);
@@ -609,7 +698,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
public static final GT_Recipe_Map sBlastRecipes = new GT_Recipe_Map(new HashSet<>(800), "gt.recipe.blastfurnace", "Blast Furnace", null, RES_PATH_GUI + "basicmachines/Default", 2, 2, 1, 0, 1, "Heat Capacity: ", 1, " K", false, true);
public static final GT_Recipe_Map sPrimitiveBlastRecipes = new GT_Recipe_Map(new HashSet<>(200), "gt.recipe.primitiveblastfurnace", "Primitive Blast Furnace", null, RES_PATH_GUI + "basicmachines/Default", 3, 3, 1, 0, 1, E, 1, E, false, true);
public static final GT_Recipe_Map sImplosionRecipes = new GT_Recipe_Map(new HashSet<>(900), "gt.recipe.implosioncompressor", "Implosion Compressor", null, RES_PATH_GUI + "basicmachines/Default", 2, 2, 2, 0, 1, E, 1, E, true, true);
- public static final GT_Recipe_Map sVacuumRecipes = new GT_Recipe_Map(new HashSet<>(305), "gt.recipe.vacuumfreezer", "Vacuum Freezer", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 1, 0, 1, E, 1, E, false, true);
+ public static final GT_Recipe_Map sVacuumRecipes = new GT_Recipe_Map(new HashSet<>(305), "gt.recipe.vacuumfreezer", "Vacuum Freezer", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, E, 1, E, false, true);
public static final GT_Recipe_Map sChemicalRecipes = new GT_Recipe_Map(new HashSet<>(1170), "gt.recipe.chemicalreactor", "Chemical Reactor", null, RES_PATH_GUI + "basicmachines/ChemicalReactor", 2, 2, 1, 0, 1, E, 1, E, true, true);
public static final GT_Recipe_Map sMultiblockChemicalRecipes = new GT_Recipe_Map_LargeChemicalReactor();
public static final GT_Recipe_Map sDistillationRecipes = new GT_Recipe_Map_DistillationTower();
@@ -646,7 +735,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
public static final GT_Recipe_Map_Fuel sHugeNaquadahReactorFuels = new GT_Recipe_Map_Fuel(new HashSet<>(1), "gt.recipe.fluidnaquadahreactor", "Naquadah Reactor MkIII", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, "Fuel Value: ", 1000, " EU", true, true);
public static final GT_Recipe_Map_Fuel sExtremeNaquadahReactorFuels = new GT_Recipe_Map_Fuel(new HashSet<>(1), "gt.recipe.hugenaquadahreactor", "Naquadah Reactor MkIV", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, "Fuel Value: ", 1000, " EU", true, true);
public static final GT_Recipe_Map_Fuel sUltraHugeNaquadahReactorFuels = new GT_Recipe_Map_Fuel(new HashSet<>(1), "gt.recipe.extrahugenaquadahreactor", "Naquadah Reactor MkV", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, "Fuel Value: ", 1000, " EU", true, true);
- public static final GT_Recipe_Map_Fuel sFluidNaquadahReactorFuels = new GT_Recipe_Map_Fuel(new HashSet<>(1), "gt.recipe.fluidnaquadahreactor", "Fluid Naquadah Reactor", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, "Fuel Value: ", 1000, " EU", true, true);
+ public static final GT_Recipe_Map_Fuel sFluidNaquadahReactorFuels = new GT_Recipe_Map_Fuel(new HashSet<>(1), "gt.recipe.fluidfuelnaquadahreactor", "Fluid Naquadah Reactor", null, RES_PATH_GUI + "basicmachines/Default", 1, 1, 0, 0, 1, "Fuel Value: ", 1000, " EU", true, true);
public static final GT_Recipe_Map_LargeBoilerFakeFuels sLargeBoilerFakeFuels = new GT_Recipe_Map_LargeBoilerFakeFuels();
/**
@@ -679,6 +768,12 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
public final boolean mNEIAllowed, mShowVoltageAmperageInNEI;
/**
+ * Unique identifier for this recipe map. Generated from aUnlocalizedName and a few other parameters.
+ * See constructor for details.
+ */
+ public final String mUniqueIdentifier;
+
+ /**
* Initialises a new type of Recipe Handler.
*
* @param aRecipeList a List you specify as Recipe List. Usually just an ArrayList with a pre-initialised Size.
@@ -710,6 +805,9 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
GregTech_API.sFluidMappings.add(mRecipeFluidMap);
GregTech_API.sItemStackMappings.add(mRecipeItemMap);
GT_LanguageManager.addStringLocalization(mUnlocalizedName = aUnlocalizedName, aLocalName);
+ mUniqueIdentifier = String.format("%s_%d_%d_%d_%d_%d", aUnlocalizedName, aAmperage, aUsualInputCount, aUsualOutputCount, aMinimalInputFluids, aMinimalInputItems);
+ if (sIndexedMappings.put(mUniqueIdentifier, this) != null)
+ throw new IllegalArgumentException("Duplicate recipe map registered: " + mUniqueIdentifier);
}
public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial, int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
@@ -793,7 +891,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
* @return if this Item is a valid Input for any for the Recipes
*/
public boolean containsInput(ItemStack aStack) {
- return aStack != null && (mRecipeItemMap.containsKey(new GT_ItemStack(aStack)) || mRecipeItemMap.containsKey(new GT_ItemStack(GT_Utility.copyMetaData(W, aStack))));
+ return aStack != null && (mRecipeItemMap.containsKey(new GT_ItemStack(aStack)) || mRecipeItemMap.containsKey(new GT_ItemStack(aStack, true)));
}
/**
@@ -879,7 +977,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
if (tRecipes != null) for (GT_Recipe tRecipe : tRecipes)
if (!tRecipe.mFakeRecipe && tRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs))
return tRecipe.mEnabled && aVoltage * mAmperage >= tRecipe.mEUt ? tRecipe : null;
- tRecipes = mRecipeItemMap.get(new GT_ItemStack(GT_Utility.copyMetaData(W, tStack)));
+ tRecipes = mRecipeItemMap.get(new GT_ItemStack(tStack, true));
if (tRecipes != null) for (GT_Recipe tRecipe : tRecipes)
if (!tRecipe.mFakeRecipe && tRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs))
return tRecipe.mEnabled && aVoltage * mAmperage >= tRecipe.mEUt ? tRecipe : null;
@@ -1025,6 +1123,10 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
tFluid.amount = 0;
mRecipesByFluidInput.put(tFluid.getUnlocalizedName(), aRecipe);
}
+ } else if ((aRecipe.mInputs == null || GT_Utility.getNonnullElementCount(aRecipe.mInputs) == 0) &&
+ aRecipe.mFluidInputs != null && GT_Utility.getNonnullElementCount(aRecipe.mFluidInputs) == 1 &&
+ aRecipe.mFluidInputs[0] != null) {
+ mRecipesByFluidInput.put(aRecipe.mFluidInputs[0].getUnlocalizedName(), aRecipe);
}
return aRecipe;
}
diff --git a/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java
new file mode 100644
index 0000000000..469f2d64a1
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java
@@ -0,0 +1,284 @@
+package gregtech.api.util;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_MultiBlockBase;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Used by machines that are locked to a single recipe, for fast computation. */
+public class GT_Single_Recipe_Check {
+ protected final GT_MetaTileEntity_MultiBlockBase multiBlockBase;
+ protected final GT_Recipe recipe;
+ protected final ImmutableMap<ItemId, Integer> itemCost;
+ protected final ImmutableMap<Fluid, Integer> fluidCost;
+
+ protected final int totalItemCost;
+ protected final int totalFluidCost;
+
+ protected GT_Single_Recipe_Check(
+ GT_MetaTileEntity_MultiBlockBase multiBlockBase,
+ GT_Recipe recipe,
+ ImmutableMap<ItemId, Integer> itemCost,
+ ImmutableMap<Fluid, Integer> fluidCost) {
+ this.multiBlockBase = multiBlockBase;
+ this.recipe = recipe;
+ this.itemCost = itemCost;
+ this.fluidCost = fluidCost;
+
+ this.totalItemCost = itemCost.values().stream().mapToInt(Integer::intValue).sum();
+ this.totalFluidCost = fluidCost.values().stream().mapToInt(Integer::intValue).sum();
+ }
+
+ public GT_Recipe getRecipe() {
+ return recipe;
+ }
+
+ /**
+ * Use this method if recipes cannot require more than a single stack of any item or fluid.
+ * In particular, {@link GT_MetaTileEntity_MultiBlockBase#getCompactedInputs()} and
+ * {@link GT_MetaTileEntity_MultiBlockBase#getCompactedFluids()} both enforce this single-stack
+ * restriction, so any multi-block that calls those can use this method.
+ */
+ public boolean checkRecipeInputsSingleStack(boolean consumeInputs) {
+ Map<ItemId, ItemStack> itemMap = null;
+ if (totalItemCost > 0) {
+ itemMap = new HashMap<>();
+ for (ItemStack itemStack : multiBlockBase.getStoredInputs()) {
+ itemMap.merge(
+ ItemId.createNoCopy(itemStack), itemStack,
+ (a, b) -> a.stackSize >= b.stackSize ? a : b);
+ }
+
+ for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) {
+ ItemStack itemStack = itemMap.get(entry.getKey());
+ if (itemStack == null || itemStack.stackSize < entry.getValue()) {
+ return false;
+ }
+ }
+ }
+
+ Map<Fluid, FluidStack> fluidMap = null;
+ if (totalFluidCost > 0) {
+ fluidMap = new HashMap<>();
+ for (FluidStack fluidStack : multiBlockBase.getStoredFluids()) {
+ fluidMap.merge(
+ fluidStack.getFluid(), fluidStack,
+ (a, b) -> a.amount >= b.amount ? a : b);
+ }
+
+ for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) {
+ FluidStack fluidStack = fluidMap.get(entry.getKey());
+ if (fluidStack == null || fluidStack.amount < entry.getValue()) {
+ return false;
+ }
+ }
+ }
+
+ if (consumeInputs) {
+ if (totalItemCost > 0) {
+ for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) {
+ itemMap.get(entry.getKey()).stackSize -= entry.getValue();
+ }
+ }
+
+ if (totalFluidCost > 0) {
+ for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) {
+ fluidMap.get(entry.getKey()).amount -= entry.getValue();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Use this method if recipes can require more than a single stack of any item or fluid.
+ * It is less efficient, though.
+ */
+ public boolean checkRecipeInputs(boolean consumeInputs) {
+ List<ItemStack> items = null;
+ if (totalItemCost > 0) {
+ items = multiBlockBase.getStoredInputs();
+
+ Map<ItemId, Integer> itemMap = new HashMap<>();
+ for (ItemStack itemStack : items) {
+ itemMap.merge(ItemId.createNoCopy(itemStack), itemStack.stackSize, Integer::sum);
+ }
+
+ for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) {
+ if (itemMap.getOrDefault(entry.getKey(), 0) < entry.getValue()) {
+ return false;
+ }
+ }
+ }
+
+ List<FluidStack> fluids = null;
+ if (totalFluidCost > 0) {
+ fluids = multiBlockBase.getStoredFluids();
+
+ Map<Fluid, Integer> fluidMap = new HashMap<>();
+ for (FluidStack fluidStack : fluids) {
+ fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum);
+ }
+
+ for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) {
+ if (fluidMap.getOrDefault(entry.getKey(), 0) < entry.getValue()) {
+ return false;
+ }
+ }
+ }
+
+ if (consumeInputs) {
+ if (totalItemCost > 0) {
+ int remainingItemCost = totalItemCost;
+ Map<ItemId, Integer> runningItemCost = Maps.newHashMap(itemCost);
+ for (ItemStack itemStack : items) {
+ ItemId key = ItemId.createNoCopy(itemStack);
+ int runningCost = runningItemCost.getOrDefault(key, 0);
+ int paid = Math.min(itemStack.stackSize, runningCost);
+ itemStack.stackSize -= paid;
+ runningItemCost.put(key, runningCost - paid);
+
+ remainingItemCost -= paid;
+ if (remainingItemCost <= 0) {
+ break;
+ }
+ }
+ }
+
+ if (totalFluidCost > 0) {
+ int remainingFluidCost = totalFluidCost;
+ Map<Fluid, Integer> runningFluidCost = Maps.newHashMap(fluidCost);
+ for (FluidStack fluidStack : fluids) {
+ Fluid key = fluidStack.getFluid();
+ int runningCost = runningFluidCost.getOrDefault(key, 0);
+ int paid = Math.min(fluidStack.amount, runningCost);
+ fluidStack.amount -= paid;
+ runningFluidCost.put(key, runningCost - paid);
+
+ remainingFluidCost -= paid;
+ if (remainingFluidCost <= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected static Map<ItemId, Integer> buildItemMap(
+ GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ Map<ItemId, Integer> itemMap = new HashMap<>();
+ for (ItemStack itemStack : multiBlockBase.getStoredInputs()) {
+ itemMap.merge(ItemId.create(itemStack), itemStack.stackSize, Integer::sum);
+ }
+ return itemMap;
+ }
+
+ protected static Map<Fluid, Integer> buildFluidMap(
+ GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ Map<Fluid, Integer> fluidMap = new HashMap<>();
+ for (FluidStack fluidStack : multiBlockBase.getStoredFluids()) {
+ fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum);
+ }
+ return fluidMap;
+ }
+
+ public static Builder builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ return new Builder(multiBlockBase);
+ }
+
+ public static final class Builder {
+ private final GT_MetaTileEntity_MultiBlockBase multiBlockBase;
+
+ // In order to compute which items and fluids are consumed by the recipe, we compare the
+ // multi-block's items and fluids before and after inputs are consumed by the recipe.
+ private Map<ItemId, Integer> beforeItems;
+ private Map<Fluid, Integer> beforeFluids;
+ private Map<ItemId, Integer> afterItems;
+ private Map<Fluid, Integer> afterFluids;
+
+ private GT_Recipe recipe;
+
+ private Builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ this.multiBlockBase = multiBlockBase;
+ }
+
+ /** Call this before inputs are consumed by the recipe. */
+ public Builder setBefore() {
+ beforeItems = buildItemMap(multiBlockBase);
+ beforeFluids = buildFluidMap(multiBlockBase);
+ return this;
+ }
+
+ /** Call this after inputs are consumed by the recipe. */
+ public Builder setAfter() {
+ afterItems = buildItemMap(multiBlockBase);
+ afterFluids = buildFluidMap(multiBlockBase);
+ return this;
+ }
+
+ public Builder setRecipe(GT_Recipe recipe) {
+ this.recipe = recipe;
+ return this;
+ }
+
+ public GT_Single_Recipe_Check build() {
+ ImmutableMap.Builder<ItemId, Integer> itemCostBuilder = ImmutableMap.builder();
+ for (Map.Entry<ItemId, Integer> entry : beforeItems.entrySet()) {
+ int cost = entry.getValue() - afterItems.getOrDefault(entry.getKey(), 0);
+ if (cost > 0) {
+ itemCostBuilder.put(entry.getKey(), cost);
+ }
+ }
+
+ ImmutableMap.Builder<Fluid, Integer> fluidCostBuilder = ImmutableMap.builder();
+ for (Map.Entry<Fluid, Integer> entry : beforeFluids.entrySet()) {
+ int cost = entry.getValue() - afterFluids.getOrDefault(entry.getKey(), 0);
+ if (cost > 0) {
+ fluidCostBuilder.put(entry.getKey(), cost);
+ }
+ }
+
+ return new GT_Single_Recipe_Check(
+ multiBlockBase, recipe, itemCostBuilder.build(), fluidCostBuilder.build());
+ }
+ }
+
+ @AutoValue
+ protected abstract static class ItemId {
+ /** This method copies NBT, as it is mutable. */
+ protected static ItemId create(ItemStack itemStack) {
+ NBTTagCompound nbt = itemStack.getTagCompound();
+ if (nbt != null) {
+ nbt = (NBTTagCompound) nbt.copy();
+ }
+
+ return new AutoValue_GT_Single_Recipe_Check_ItemId(
+ itemStack.getItem(), itemStack.getItemDamage(), nbt);
+ }
+
+ /** This method does not copy NBT in order to save time. Make sure not to mutate it! */
+ protected static ItemId createNoCopy(ItemStack itemStack) {
+ return new AutoValue_GT_Single_Recipe_Check_ItemId(
+ itemStack.getItem(), itemStack.getItemDamage(), itemStack.getTagCompound());
+ }
+
+ protected abstract Item item();
+ protected abstract int metaData();
+
+ @Nullable
+ protected abstract NBTTagCompound nbt();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java
new file mode 100644
index 0000000000..225e0bd723
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java
@@ -0,0 +1,212 @@
+package gregtech.api.util;
+
+import com.google.common.collect.ImmutableMap;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_MultiBlockBase;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Processing Array-specialized version of {@link gregtech.api.util.GT_Single_Recipe_Check}. */
+public class GT_Single_Recipe_Check_Processing_Array extends GT_Single_Recipe_Check {
+ protected final int recipeAmperage;
+
+ /**
+ * The machine type that the locked recipe is for.
+ * We will not allow running with other machines.
+ */
+ protected final ItemStack machineStack;
+
+ protected GT_Single_Recipe_Check_Processing_Array(
+ GT_MetaTileEntity_MultiBlockBase multiBlockBase,
+ GT_Recipe recipe,
+ ImmutableMap<ItemId, Integer> itemCost,
+ ImmutableMap<Fluid, Integer> fluidCost,
+ int recipeAmperage,
+ ItemStack machineStack) {
+ super(multiBlockBase, recipe, itemCost, fluidCost);
+
+ this.recipeAmperage = recipeAmperage;
+ this.machineStack = machineStack;
+ }
+
+ public int getRecipeAmperage() {
+ return recipeAmperage;
+ }
+
+ @Override
+ public boolean checkRecipeInputsSingleStack(boolean consumeInputs) {
+ throw new UnsupportedOperationException("Use checkRecipeInputs(boolean, int) instead.");
+ }
+
+ @Override
+ public boolean checkRecipeInputs(boolean consumeInputs) {
+ throw new UnsupportedOperationException("Use checkRecipeInputs(boolean, int) instead.");
+ }
+
+ /** Returns the number of parallel recipes, or 0 if recipe is not satisfied at all. */
+ public int checkRecipeInputs(boolean consumeInputs, int maxParallel) {
+ if (!GT_Utility.areStacksEqual(machineStack, multiBlockBase.mInventory[1])) {
+ // Machine stack was modified. This is not allowed, so stop processing.
+ return 0;
+ }
+ int parallel = maxParallel;
+
+ List<ItemStack> items = null;
+ if (totalItemCost > 0) {
+ items = multiBlockBase.getStoredInputs();
+
+ Map<ItemId, Integer> itemMap = new HashMap<>();
+ for (ItemStack itemStack : items) {
+ itemMap.merge(ItemId.createNoCopy(itemStack), itemStack.stackSize, Integer::sum);
+ }
+
+ for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) {
+ parallel = Math.min(parallel, itemMap.getOrDefault(entry.getKey(), 0) / entry.getValue());
+ if (parallel <= 0) {
+ return 0;
+ }
+ }
+ }
+
+ List<FluidStack> fluids = null;
+ if (totalFluidCost > 0) {
+ fluids = multiBlockBase.getStoredFluids();
+
+ Map<Fluid, Integer> fluidMap = new HashMap<>();
+ for (FluidStack fluidStack : fluids) {
+ fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum);
+ }
+
+ for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) {
+ parallel = Math.min(parallel, fluidMap.getOrDefault(entry.getKey(), 0) / entry.getValue());
+ if (parallel <= 0) {
+ return 0;
+ }
+ }
+ }
+
+ final int finalParallel = parallel;
+ if (consumeInputs) {
+ if (totalItemCost > 0) {
+ int remainingItemCost = totalItemCost * finalParallel;
+ Map<ItemId, Integer> runningItemCost =
+ itemCost.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue() * finalParallel));
+
+ for (ItemStack itemStack : items) {
+ ItemId key = ItemId.createNoCopy(itemStack);
+ int runningCost = runningItemCost.getOrDefault(key, 0);
+ int paid = Math.min(itemStack.stackSize, runningCost);
+ itemStack.stackSize -= paid;
+ runningItemCost.put(key, runningCost - paid);
+
+ remainingItemCost -= paid;
+ if (remainingItemCost <= 0) {
+ break;
+ }
+ }
+ }
+
+ if (totalFluidCost > 0) {
+ int remainingFluidCost = totalFluidCost * finalParallel;
+ Map<Fluid, Integer> runningFluidCost =
+ fluidCost.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue() * finalParallel));
+
+ for (FluidStack fluidStack : fluids) {
+ Fluid key = fluidStack.getFluid();
+ int runningCost = runningFluidCost.getOrDefault(key, 0);
+ int paid = Math.min(fluidStack.amount, runningCost);
+ fluidStack.amount -= paid;
+ runningFluidCost.put(key, runningCost - paid);
+
+ remainingFluidCost -= paid;
+ if (remainingFluidCost <= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ return finalParallel;
+ }
+
+ public static Builder processingArrayBuilder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ return new Builder(multiBlockBase);
+ }
+
+ public static final class Builder {
+ private final GT_MetaTileEntity_MultiBlockBase multiBlockBase;
+
+ // In order to compute which items and fluids are consumed by the recipe, we compare the
+ // multi-block's items and fluids before and after inputs are consumed by the recipe.
+ private Map<ItemId, Integer> beforeItems;
+ private Map<Fluid, Integer> beforeFluids;
+ private Map<ItemId, Integer> afterItems;
+ private Map<Fluid, Integer> afterFluids;
+
+ private GT_Recipe recipe;
+ private int recipeAmperage;
+
+ private Builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) {
+ this.multiBlockBase = multiBlockBase;
+ }
+
+ /** Call this before inputs are consumed by the recipe. */
+ public Builder setBefore() {
+ beforeItems = buildItemMap(multiBlockBase);
+ beforeFluids = buildFluidMap(multiBlockBase);
+ return this;
+ }
+
+ /** Call this after inputs are consumed by the recipe. */
+ public Builder setAfter() {
+ afterItems = buildItemMap(multiBlockBase);
+ afterFluids = buildFluidMap(multiBlockBase);
+ return this;
+ }
+
+ public Builder setRecipe(GT_Recipe recipe) {
+ this.recipe = recipe;
+ return this;
+ }
+
+ public Builder setRecipeAmperage(int recipeAmperage) {
+ this.recipeAmperage = recipeAmperage;
+ return this;
+ }
+
+ public GT_Single_Recipe_Check_Processing_Array build() {
+ ImmutableMap.Builder<ItemId, Integer> itemCostBuilder = ImmutableMap.builder();
+ for (Map.Entry<ItemId, Integer> entry : beforeItems.entrySet()) {
+ int cost = entry.getValue() - afterItems.getOrDefault(entry.getKey(), 0);
+ if (cost > 0) {
+ itemCostBuilder.put(entry.getKey(), cost);
+ }
+ }
+
+ ImmutableMap.Builder<Fluid, Integer> fluidCostBuilder = ImmutableMap.builder();
+ for (Map.Entry<Fluid, Integer> entry : beforeFluids.entrySet()) {
+ int cost = entry.getValue() - afterFluids.getOrDefault(entry.getKey(), 0);
+ if (cost > 0) {
+ fluidCostBuilder.put(entry.getKey(), cost);
+ }
+ }
+
+ return new GT_Single_Recipe_Check_Processing_Array(
+ multiBlockBase, recipe, itemCostBuilder.build(), fluidCostBuilder.build(),
+ recipeAmperage, multiBlockBase.mInventory[1].copy());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java b/src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java
new file mode 100644
index 0000000000..271b361da0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java
@@ -0,0 +1,61 @@
+package gregtech.api.util;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+
+public class GT_ToolHarvestHelper {
+
+ public static boolean isAppropriateTool(Block aBlock, byte aMetaData, String... tTools) {
+
+ if (aBlock == null || tTools == null) {
+ return false;
+ }
+ String targetTool = aBlock.getHarvestTool(aMetaData);
+ return !isStringEmpty(targetTool) && isArrayContains(targetTool, tTools);
+ }
+
+ public static boolean isAppropriateMaterial(Block aBlock, Material... tMats) {
+ if (aBlock == null || tMats == null) {
+ return false;
+ }
+ return isArrayContains(aBlock.getMaterial(), tMats);
+ }
+
+
+ public static boolean isSpecialBlock(Block aBlock, Block... tBlocks) {
+ if (aBlock == null || tBlocks == null) {
+ return false;
+ }
+ return isArrayContains(aBlock, tBlocks);
+ }
+
+
+ public static <T> boolean isArrayContains(T obj, T[] list) {
+
+ if (obj == null || list == null) {
+ return false;
+ }
+
+ for (T iObj : list) {
+ if (obj == iObj || obj.equals(iObj)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isStringEmpty(String s) {
+ return s == null || s.length() == 0;
+ }
+
+ public static boolean hasNull(Object... obj) {
+ for (Object iObj : obj) {
+ if (iObj == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+}
diff --git a/src/main/java/gregtech/api/util/GT_Utility.java b/src/main/java/gregtech/api/util/GT_Utility.java
index be5f978d6e..a07868ec68 100644
--- a/src/main/java/gregtech/api/util/GT_Utility.java
+++ b/src/main/java/gregtech/api/util/GT_Utility.java
@@ -1,6 +1,7 @@
package gregtech.api.util;
import cofh.api.transport.IItemDuct;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
@@ -11,13 +12,24 @@ import gregtech.api.GregTech_API;
import gregtech.api.damagesources.GT_DamageSources;
import gregtech.api.damagesources.GT_DamageSources.DamageSourceHotItem;
import gregtech.api.enchants.Enchantment_Radioactivity;
-import gregtech.api.enums.*;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SubTag;
+import gregtech.api.enums.Textures;
+import gregtech.api.enums.ToolDictNames;
import gregtech.api.events.BlockScanningEvent;
import gregtech.api.interfaces.IBlockContainer;
import gregtech.api.interfaces.IDebugableBlock;
import gregtech.api.interfaces.IProjectileItem;
import gregtech.api.interfaces.ITexture;
-import gregtech.api.interfaces.tileentity.*;
+import gregtech.api.interfaces.tileentity.IBasicEnergyContainer;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.interfaces.tileentity.IGregTechDeviceInformation;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.interfaces.tileentity.IMachineProgress;
+import gregtech.api.interfaces.tileentity.IUpgradableMachine;
import gregtech.api.items.GT_EnergyArmor_Item;
import gregtech.api.items.GT_Generic_Item;
import gregtech.api.items.GT_MetaGenerated_Tool;
@@ -27,7 +39,8 @@ import gregtech.api.objects.GT_ItemStack;
import gregtech.api.objects.ItemData;
import gregtech.api.threads.GT_Runnable_Sound;
import gregtech.api.util.extensions.ArrayExt;
-import gregtech.common.GT_Proxy;
+import gregtech.common.GT_Pollution;
+import gregtech.common.blocks.GT_Block_Ores_Abstract;
import ic2.api.recipe.IRecipeInput;
import ic2.api.recipe.RecipeInputItemStack;
import ic2.api.recipe.RecipeInputOreDict;
@@ -35,7 +48,11 @@ import ic2.api.recipe.RecipeOutput;
import net.minecraft.block.Block;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
-import net.minecraft.entity.*;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.EntityLiving;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.EnumCreatureAttribute;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -57,9 +74,14 @@ import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
-import net.minecraft.util.*;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.DamageSource;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
+import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.BlockSnapshot;
@@ -68,23 +90,39 @@ import net.minecraftforge.common.util.FakePlayerFactory;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.world.BlockEvent;
-import net.minecraftforge.fluids.*;
+import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry.FluidContainerData;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+import net.minecraftforge.fluids.IFluidContainerItem;
+import net.minecraftforge.fluids.IFluidHandler;
import net.minecraftforge.oredict.OreDictionary;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
import java.util.*;
import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Function;
import java.util.function.IntFunction;
+import java.util.function.Supplier;
import static gregtech.GT_Mod.GT_FML_LOGGER;
-import static gregtech.api.enums.GT_Values.*;
-import static gregtech.common.GT_Proxy.GTPOLLUTION;
+import static gregtech.api.enums.GT_Values.D1;
+import static gregtech.api.enums.GT_Values.DW;
+import static gregtech.api.enums.GT_Values.E;
+import static gregtech.api.enums.GT_Values.GT;
+import static gregtech.api.enums.GT_Values.L;
+import static gregtech.api.enums.GT_Values.M;
+import static gregtech.api.enums.GT_Values.NW;
+import static gregtech.api.enums.GT_Values.V;
+import static gregtech.api.enums.GT_Values.W;
import static gregtech.common.GT_UndergroundOil.undergroundOilReadInformation;
/**
@@ -94,7 +132,7 @@ import static gregtech.common.GT_UndergroundOil.undergroundOilReadInformation;
*/
public class GT_Utility {
/** Formats a number with group separator and at most 2 fraction digits. */
- private static final DecimalFormat decimalFormat = new DecimalFormat();
+ private static final NumberFormat numberFormat = NumberFormat.getInstance();
/**
* Forge screwed the Fluid Registry up again, so I make my own, which is also much more efficient than the stupid Stuff over there.
@@ -102,6 +140,9 @@ public class GT_Utility {
private static final List<FluidContainerData> sFluidContainerList = new ArrayList<>();
private static final Map<GT_ItemStack, FluidContainerData> sFilledContainerToData = new /*Concurrent*/HashMap<>();
private static final Map<GT_ItemStack, Map<Fluid, FluidContainerData>> sEmptyContainerToFluidToData = new /*Concurrent*/HashMap<>();
+ private static final Map<Fluid, List<ItemStack>> sFluidToContainers = new HashMap<>();
+ /** Must use {@code Supplier} here because the ore prefixes have not yet been registered at class load time. */
+ private static final Map<OrePrefixes, Supplier<ItemStack>> sOreToCobble = new HashMap<>();
public static volatile int VERSION = 509;
public static boolean TE_CHECK = false, BC_CHECK = false, CHECK_ALL = true, RF_CHECK = false;
public static Map<GT_PlayedSound, Integer> sPlayedSoundMap = new /*Concurrent*/HashMap<>();
@@ -109,13 +150,33 @@ public class GT_Utility {
public static UUID defaultUuid = null; // maybe default non-null? UUID.fromString("00000000-0000-0000-0000-000000000000");
static {
- DecimalFormatSymbols symbols = decimalFormat.getDecimalFormatSymbols();
- symbols.setGroupingSeparator(' ');
- decimalFormat.setDecimalFormatSymbols(symbols);
- decimalFormat.setMaximumFractionDigits(2);
+ numberFormat.setMaximumFractionDigits(2);
GregTech_API.sItemStackMappings.add(sFilledContainerToData);
GregTech_API.sItemStackMappings.add(sEmptyContainerToFluidToData);
+
+ // 1 is the magic index to get the cobblestone block.
+ // See: GT_Block_Stones.java, GT_Block_Granites.java
+ Function<Materials, Supplier<ItemStack>> materialToCobble =
+ m -> Suppliers.memoize(() -> GT_OreDictUnificator.getOres(OrePrefixes.stone, m).get(1))::get;
+ sOreToCobble.put(
+ OrePrefixes.oreBlackgranite,
+ materialToCobble.apply(Materials.GraniteBlack));
+ sOreToCobble.put(
+ OrePrefixes.oreRedgranite,
+ materialToCobble.apply(Materials.GraniteRed));
+ sOreToCobble.put(
+ OrePrefixes.oreMarble,
+ materialToCobble.apply(Materials.Marble));
+ sOreToCobble.put(
+ OrePrefixes.oreBasalt,
+ materialToCobble.apply(Materials.Basalt));
+ sOreToCobble.put(
+ OrePrefixes.oreNetherrack,
+ () -> new ItemStack(Blocks.netherrack));
+ sOreToCobble.put(
+ OrePrefixes.oreEndstone,
+ () -> new ItemStack(Blocks.end_stone));
}
public static int safeInt(long number, int margin){
@@ -918,14 +979,22 @@ public class GT_Utility {
public static void reInit() {
sFilledContainerToData.clear();
sEmptyContainerToFluidToData.clear();
+ sFluidToContainers.clear();
for (FluidContainerData tData : sFluidContainerList) {
sFilledContainerToData.put(new GT_ItemStack(tData.filledContainer), tData);
Map<Fluid, FluidContainerData> tFluidToContainer = sEmptyContainerToFluidToData.get(new GT_ItemStack(tData.emptyContainer));
+ List<ItemStack> tContainers = sFluidToContainers.get(tData.fluid.getFluid());
if (tFluidToContainer == null) {
sEmptyContainerToFluidToData.put(new GT_ItemStack(tData.emptyContainer), tFluidToContainer = new /*Concurrent*/HashMap<>());
GregTech_API.sFluidMappings.add(tFluidToContainer);
}
tFluidToContainer.put(tData.fluid.getFluid(), tData);
+ if (tContainers == null) {
+ tContainers = new ArrayList<>();
+ tContainers.add(tData.filledContainer);
+ sFluidToContainers.put(tData.fluid.getFluid(), tContainers);
+ }
+ else tContainers.add(tData.filledContainer);
}
}
@@ -933,11 +1002,27 @@ public class GT_Utility {
sFluidContainerList.add(aData);
sFilledContainerToData.put(new GT_ItemStack(aData.filledContainer), aData);
Map<Fluid, FluidContainerData> tFluidToContainer = sEmptyContainerToFluidToData.get(new GT_ItemStack(aData.emptyContainer));
+ List<ItemStack> tContainers = sFluidToContainers.get(aData.fluid.getFluid());
if (tFluidToContainer == null) {
sEmptyContainerToFluidToData.put(new GT_ItemStack(aData.emptyContainer), tFluidToContainer = new /*Concurrent*/HashMap<>());
GregTech_API.sFluidMappings.add(tFluidToContainer);
}
tFluidToContainer.put(aData.fluid.getFluid(), aData);
+ if (tContainers == null) {
+ tContainers = new ArrayList<>();
+ tContainers.add(aData.filledContainer);
+ sFluidToContainers.put(aData.fluid.getFluid(), tContainers);
+ }
+ else tContainers.add(aData.filledContainer);
+ }
+
+ public static List<ItemStack> getContainersFromFluid(FluidStack tFluidStack) {
+ if (tFluidStack != null) {
+ List<ItemStack> tContainers = sFluidToContainers.get(tFluidStack.getFluid());
+ if (tContainers == null) return new ArrayList<>();
+ return tContainers;
+ }
+ return new ArrayList<>();
}
public static ItemStack fillFluidContainer(FluidStack aFluid, ItemStack aStack, boolean aRemoveFluidDirectly, boolean aCheckIFluidContainerItems) {
@@ -2063,7 +2148,7 @@ public class GT_Utility {
try {
if (tTileEntity instanceof ICoverable) {
rEUAmount += 300;
- String tString = ((ICoverable) tTileEntity).getCoverBehaviorAtSide((byte) aSide).getDescription((byte) aSide, ((ICoverable) tTileEntity).getCoverIDAtSide((byte) aSide), ((ICoverable) tTileEntity).getCoverDataAtSide((byte) aSide), (ICoverable) tTileEntity);
+ String tString = ((ICoverable) tTileEntity).getCoverBehaviorAtSideNew((byte) aSide).getDescription((byte) aSide, ((ICoverable) tTileEntity).getCoverIDAtSide((byte) aSide), ((ICoverable) tTileEntity).getComplexCoverDataAtSide((byte) aSide), (ICoverable) tTileEntity);
if (tString != null && !tString.equals(E)) tList.add(tString);
}
} catch (Throwable e) {
@@ -2135,23 +2220,19 @@ public class GT_Utility {
}
}
+ Chunk currentChunk = aWorld.getChunkFromBlockCoords(aX, aZ);
if (aPlayer.capabilities.isCreativeMode) {
- FluidStack tFluid = undergroundOilReadInformation(aWorld.getChunkFromBlockCoords(aX,aZ));//-# to only read
+ FluidStack tFluid = undergroundOilReadInformation(currentChunk);//-# to only read
if (tFluid!=null)
tList.add(EnumChatFormatting.GOLD + tFluid.getLocalizedName() + EnumChatFormatting.RESET + ": " + EnumChatFormatting.YELLOW + formatNumbers(tFluid.amount) + EnumChatFormatting.RESET + " L");
else
tList.add(EnumChatFormatting.GOLD + trans("201","Nothing") + EnumChatFormatting.RESET + ": " + EnumChatFormatting.YELLOW + '0' + EnumChatFormatting.RESET + " L");
}
// if(aPlayer.capabilities.isCreativeMode){
- int[] chunkData = GT_Proxy.dimensionWiseChunkData.get(aWorld.provider.dimensionId).get(aWorld.getChunkFromBlockCoords(aX,aZ).getChunkCoordIntPair());
- if(chunkData != null){
- if(chunkData[GTPOLLUTION]>0){
- tList.add(trans("202","Pollution in Chunk: ") + EnumChatFormatting.RED + formatNumbers(chunkData[GTPOLLUTION]) + EnumChatFormatting.RESET + trans("203"," gibbl"));
- }else{
- tList.add(EnumChatFormatting.GREEN+trans("204","No Pollution in Chunk! HAYO!")+EnumChatFormatting.RESET);
- }
- }else{
- tList.add(EnumChatFormatting.GREEN+trans("204","No Pollution in Chunk! HAYO!")+EnumChatFormatting.RESET);
+ if (GT_Pollution.hasPollution(currentChunk)) {
+ tList.add(trans("202", "Pollution in Chunk: ") + EnumChatFormatting.RED + formatNumbers(GT_Pollution.getPollution(currentChunk)) + EnumChatFormatting.RESET + trans("203", " gibbl"));
+ } else {
+ tList.add(EnumChatFormatting.GREEN + trans("204", "No Pollution in Chunk! HAYO!") + EnumChatFormatting.RESET);
}
try {
@@ -2254,11 +2335,11 @@ public class GT_Utility {
}
public static String formatNumbers(long aNumber) {
- return decimalFormat.format(aNumber);
+ return numberFormat.format(aNumber);
}
public static String formatNumbers(double aNumber) {
- return decimalFormat.format(aNumber);
+ return numberFormat.format(aNumber);
}
/*
@@ -2708,7 +2789,9 @@ public class GT_Utility {
);
public static boolean isOre(Block aBlock, int aMeta) {
- return isOre(new ItemStack(aBlock, 1, aMeta)) || ORE_BLOCK_CLASSES.contains(aBlock.getClass().getName());
+ return (aBlock instanceof GT_Block_Ores_Abstract)
+ || isOre(new ItemStack(aBlock, 1, aMeta))
+ || ORE_BLOCK_CLASSES.contains(aBlock.getClass().getName());
}
public static boolean isOre(ItemStack aStack) {
@@ -2719,6 +2802,24 @@ public class GT_Utility {
return false;
}
+ /**
+ * Do <b>NOT</b> mutate the returned {@code ItemStack}!
+ * We return {@code ItemStack} instead of {@code Block} so that we can include metadata.
+ */
+ public static ItemStack getCobbleForOre(Block ore, short metaData) {
+ // We need to convert small ores to regular ores because small ores don't have associated ItemData.
+ // We take the modulus of the metadata by 16000 because that is the magic number to convert small ores to regular ores.
+ // See: GT_TileEntity_Ores.java
+ ItemData association = GT_OreDictUnificator.getAssociation(new ItemStack(Item.getItemFromBlock(ore), 1, metaData % 16000));
+ if (association != null) {
+ Supplier<ItemStack> supplier = sOreToCobble.get(association.mPrefix);
+ if (supplier != null) {
+ return supplier.get();
+ }
+ }
+ return new ItemStack(Blocks.cobblestone);
+ }
+
public static Optional<GT_Recipe> reverseShapelessRecipe(ItemStack output, Object... aRecipe) {
if (output == null) {
return Optional.empty();
diff --git a/src/main/java/gregtech/api/util/ISerializableObject.java b/src/main/java/gregtech/api/util/ISerializableObject.java
new file mode 100644
index 0000000000..51fa40e55a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ISerializableObject.java
@@ -0,0 +1,150 @@
+package gregtech.api.util;
+
+import com.google.common.io.ByteArrayDataInput;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompressedStreamTools;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTSizeTracker;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagInt;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+/**
+ * We could well have used {@link java.io.Serializable}, but that's too slow and should generally be avoided
+ *
+ * @author glease
+ */
+public interface ISerializableObject {
+
+ @Nonnull
+ ISerializableObject copy();
+
+ @Nonnull
+ NBTBase saveDataToNBT();
+
+ /**
+ * Write data to given ByteBuf
+ * The data saved this way is intended to be stored for short amount of time over network.
+ * DO NOT store it to disks.
+ */
+ // the NBT is an unfortunate piece of tech. everything uses it but its API is not as efficient as could be
+ void writeToByteBuf(ByteBuf aBuf);
+
+ void loadDataFromNBT(NBTBase aNBT);
+
+ /**
+ * Read data from given parameter and return this.
+ * The data read this way is intended to be stored for short amount of time over network.
+ */
+ // the NBT is an unfortunate piece of tech. everything uses it but its API is not as efficient as could be
+ @Nonnull
+ ISerializableObject readFromPacket(ByteArrayDataInput aBuf, EntityPlayerMP aPlayer);
+
+ /**
+ * Reverse engineered and adapted {@link cpw.mods.fml.common.network.ByteBufUtils#readTag(ByteBuf)}
+ * Given buffer must contain a serialized NBTTagCompound in minecraft encoding
+ */
+ static NBTTagCompound readCompoundTagFromGreggyByteBuf(ByteArrayDataInput aBuf) {
+ short size = aBuf.readShort();
+ if (size < 0)
+ return null;
+ else {
+ byte[] buf = new byte[size]; // this is shit, how many copies have we been doing?
+ aBuf.readFully(buf);
+ try {
+ return CompressedStreamTools.func_152457_a(buf, new NBTSizeTracker(2097152L));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Reverse engineered and adapted {@link cpw.mods.fml.common.network.ByteBufUtils#readItemStack(ByteBuf)}
+ * Given buffer must contain a serialized ItemStack in minecraft encoding
+ */
+ static ItemStack readItemStackFromGreggyByteBuf(ByteArrayDataInput aBuf) {
+ ItemStack stack = null;
+ short id = aBuf.readShort();
+ if (id >= 0) {
+ byte size = aBuf.readByte();
+ short meta = aBuf.readShort();
+ stack = new ItemStack(Item.getItemById(id), size, meta);
+ stack.stackTagCompound = readCompoundTagFromGreggyByteBuf(aBuf);
+ }
+ return stack;
+ }
+
+ final class LegacyCoverData implements ISerializableObject {
+ private int mData;
+
+ public LegacyCoverData() {
+ }
+
+ public LegacyCoverData(int mData) {
+ this.mData = mData;
+ }
+
+ @Override
+ @Nonnull
+ public ISerializableObject copy() {
+ return new LegacyCoverData(mData);
+ }
+
+ @Override
+ @Nonnull
+ public NBTBase saveDataToNBT() {
+ return new NBTTagInt(mData);
+ }
+
+ @Override
+ public void writeToByteBuf(ByteBuf aBuf) {
+ aBuf.writeInt(mData);
+ }
+
+ @Override
+ public void loadDataFromNBT(NBTBase aNBT) {
+ mData = aNBT instanceof NBTBase.NBTPrimitive ? ((NBTBase.NBTPrimitive) aNBT).func_150287_d() : 0;
+ }
+
+ @Override
+ @Nonnull
+ public ISerializableObject readFromPacket(ByteArrayDataInput aBuf, EntityPlayerMP aPlayer) {
+ mData = aBuf.readInt();
+ return this;
+ }
+
+ public int get() {
+ return mData;
+ }
+
+ public void set(int mData) {
+ this.mData = mData;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LegacyCoverData that = (LegacyCoverData) o;
+
+ return mData == that.mData;
+ }
+
+ @Override
+ public int hashCode() {
+ return mData;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mData);
+ }
+ }
+}