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/AdvancedFusionOverclockDescriber.java23
-rw-r--r--src/main/java/gregtech/api/util/AveragePerTickCounter.java139
-rw-r--r--src/main/java/gregtech/api/util/ColorsMetadataSection.java63
-rw-r--r--src/main/java/gregtech/api/util/ColorsMetadataSectionSerializer.java81
-rw-r--r--src/main/java/gregtech/api/util/ExternalMaterials.java14
-rw-r--r--src/main/java/gregtech/api/util/FieldsAreNonnullByDefault.java18
-rw-r--r--src/main/java/gregtech/api/util/FishPondFakeRecipe.java80
-rw-r--r--src/main/java/gregtech/api/util/GT_ApiaryModifier.java24
-rw-r--r--src/main/java/gregtech/api/util/GT_ApiaryUpgrade.java225
-rw-r--r--src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java557
-rw-r--r--src/main/java/gregtech/api/util/GT_Assemblyline_Server.java297
-rw-r--r--src/main/java/gregtech/api/util/GT_BaseCrop.java311
-rw-r--r--src/main/java/gregtech/api/util/GT_BlockMap.java134
-rw-r--r--src/main/java/gregtech/api/util/GT_BlockSet.java39
-rw-r--r--src/main/java/gregtech/api/util/GT_CLS_Compat.java157
-rw-r--r--src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java494
-rw-r--r--src/main/java/gregtech/api/util/GT_CircuitryBehavior.java212
-rw-r--r--src/main/java/gregtech/api/util/GT_ClientPreference.java41
-rw-r--r--src/main/java/gregtech/api/util/GT_Config.java160
-rw-r--r--src/main/java/gregtech/api/util/GT_CoverBehavior.java411
-rw-r--r--src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java856
-rw-r--r--src/main/java/gregtech/api/util/GT_CreativeTab.java26
-rw-r--r--src/main/java/gregtech/api/util/GT_ExoticEnergyInputHelper.java114
-rw-r--r--src/main/java/gregtech/api/util/GT_FoodStat.java122
-rw-r--r--src/main/java/gregtech/api/util/GT_Forestry_Compat.java195
-rw-r--r--src/main/java/gregtech/api/util/GT_GC_Compat.java52
-rw-r--r--src/main/java/gregtech/api/util/GT_HatchElementBuilder.java524
-rw-r--r--src/main/java/gregtech/api/util/GT_IBoxableWrapper.java13
-rw-r--r--src/main/java/gregtech/api/util/GT_ItsNotMyFaultException.java18
-rw-r--r--src/main/java/gregtech/api/util/GT_JubilanceMegaApiary.java23
-rw-r--r--src/main/java/gregtech/api/util/GT_LanguageManager.java600
-rw-r--r--src/main/java/gregtech/api/util/GT_Log.java45
-rw-r--r--src/main/java/gregtech/api/util/GT_ModHandler.java2551
-rw-r--r--src/main/java/gregtech/api/util/GT_Multiblock_Tooltip_Builder.java720
-rw-r--r--src/main/java/gregtech/api/util/GT_OreDictUnificator.java570
-rw-r--r--src/main/java/gregtech/api/util/GT_OverclockCalculator.java631
-rw-r--r--src/main/java/gregtech/api/util/GT_PCBFactoryManager.java25
-rw-r--r--src/main/java/gregtech/api/util/GT_ParallelHelper.java717
-rw-r--r--src/main/java/gregtech/api/util/GT_PlayedSound.java42
-rw-r--r--src/main/java/gregtech/api/util/GT_ProcessingArray_Manager.java51
-rw-r--r--src/main/java/gregtech/api/util/GT_Recipe.java1271
-rw-r--r--src/main/java/gregtech/api/util/GT_RecipeBuilder.java933
-rw-r--r--src/main/java/gregtech/api/util/GT_RecipeConstants.java332
-rw-r--r--src/main/java/gregtech/api/util/GT_RecipeMapUtil.java226
-rw-r--r--src/main/java/gregtech/api/util/GT_RecipeRegistrator.java868
-rw-r--r--src/main/java/gregtech/api/util/GT_RenderingWorld.java195
-rw-r--r--src/main/java/gregtech/api/util/GT_Shaped_Recipe.java100
-rw-r--r--src/main/java/gregtech/api/util/GT_Shapeless_Recipe.java100
-rw-r--r--src/main/java/gregtech/api/util/GT_SpawnEventHandler.java81
-rw-r--r--src/main/java/gregtech/api/util/GT_StreamUtil.java44
-rw-r--r--src/main/java/gregtech/api/util/GT_StructureUtility.java512
-rw-r--r--src/main/java/gregtech/api/util/GT_StructureUtilityMuTE.java271
-rw-r--r--src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java71
-rw-r--r--src/main/java/gregtech/api/util/GT_TooltipDataCache.java105
-rw-r--r--src/main/java/gregtech/api/util/GT_Util.java202
-rw-r--r--src/main/java/gregtech/api/util/GT_Utility.java4982
-rw-r--r--src/main/java/gregtech/api/util/GT_UtilityClient.java52
-rw-r--r--src/main/java/gregtech/api/util/GT_Waila.java23
-rw-r--r--src/main/java/gregtech/api/util/GasSpargingRecipe.java103
-rw-r--r--src/main/java/gregtech/api/util/GasSpargingRecipeMap.java45
-rw-r--r--src/main/java/gregtech/api/util/HotFuel.java40
-rw-r--r--src/main/java/gregtech/api/util/IGT_HatchAdder.java28
-rw-r--r--src/main/java/gregtech/api/util/ISerializableObject.java159
-rw-r--r--src/main/java/gregtech/api/util/LightingHelper.java1434
-rw-r--r--src/main/java/gregtech/api/util/MethodsReturnNonnullByDefault.java22
-rw-r--r--src/main/java/gregtech/api/util/OutputHatchWrapper.java65
-rw-r--r--src/main/java/gregtech/api/util/SemiFluidFuelHandler.java136
-rw-r--r--src/main/java/gregtech/api/util/ValidationResult.java24
-rw-r--r--src/main/java/gregtech/api/util/ValidationType.java6
-rw-r--r--src/main/java/gregtech/api/util/VoidProtectionHelper.java485
-rw-r--r--src/main/java/gregtech/api/util/WorldSpawnedEventBuilder.java678
-rw-r--r--src/main/java/gregtech/api/util/extensions/ArrayExt.java81
-rw-r--r--src/main/java/gregtech/api/util/extensions/IteratorExt.java13
-rw-r--r--src/main/java/gregtech/api/util/item/ItemHolder.java79
-rw-r--r--src/main/java/gregtech/api/util/recipe/RecipeInputRequirements.java77
-rw-r--r--src/main/java/gregtech/api/util/shutdown/ReasonOutOfFluid.java63
-rw-r--r--src/main/java/gregtech/api/util/shutdown/ReasonOutOfItem.java62
-rw-r--r--src/main/java/gregtech/api/util/shutdown/ReasonOutOfStuff.java61
-rw-r--r--src/main/java/gregtech/api/util/shutdown/ShutDownReason.java41
-rw-r--r--src/main/java/gregtech/api/util/shutdown/ShutDownReasonRegistry.java118
-rw-r--r--src/main/java/gregtech/api/util/shutdown/SimpleShutDownReason.java79
81 files changed, 25642 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/util/AdvancedFusionOverclockDescriber.java b/src/main/java/gregtech/api/util/AdvancedFusionOverclockDescriber.java
new file mode 100644
index 0000000000..7a6e609f93
--- /dev/null
+++ b/src/main/java/gregtech/api/util/AdvancedFusionOverclockDescriber.java
@@ -0,0 +1,23 @@
+package gregtech.api.util;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import gregtech.api.objects.overclockdescriber.FusionOverclockDescriber;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class AdvancedFusionOverclockDescriber extends FusionOverclockDescriber {
+
+ public AdvancedFusionOverclockDescriber(byte energyTier, long capableStartup) {
+ super(energyTier, capableStartup);
+ }
+
+ @Override
+ protected int getEUtIncreasePerOC() {
+ return 2;
+ }
+
+ protected int getDurationDecreasePerOC() {
+ return 2;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/AveragePerTickCounter.java b/src/main/java/gregtech/api/util/AveragePerTickCounter.java
new file mode 100644
index 0000000000..c9b1275deb
--- /dev/null
+++ b/src/main/java/gregtech/api/util/AveragePerTickCounter.java
@@ -0,0 +1,139 @@
+package gregtech.api.util;
+
+import java.security.InvalidParameterException;
+import java.util.ArrayDeque;
+
+import net.minecraft.server.MinecraftServer;
+
+public class AveragePerTickCounter {
+
+ /**
+ * Averages a value over a certain amount of ticks
+ *
+ * @param period amount of ticks to average (20 for 1 second)
+ *
+ */
+ public AveragePerTickCounter(int period) throws InvalidParameterException {
+
+ if (period <= 0) throw new InvalidParameterException("period should be a positive non-zero number");
+
+ this.period = period;
+ values = new ArrayDeque<>(period);
+ }
+
+ public void addValue(long value) {
+
+ if (value == 0) return;
+
+ final int currTick = getWorldTimeInTicks();
+
+ if (values.isEmpty()) {
+ values.addLast(new Measurement(currTick, value));
+ isCachedAverageValid = false;
+ return;
+ }
+
+ Measurement lastMeasurement = values.peekLast();
+ final int lastMeasurementTick = lastMeasurement.TimestampInWorldTicks;
+
+ /// sums up values added in the same tick
+ /// for example a cable had an amp running through it multiple times in the same tick
+ if (currTick == lastMeasurementTick) {
+ lastMeasurement.Value = lastMeasurement.Value + value;
+ isCachedAverageValid = false;
+ return;
+ }
+
+ if (currTick > lastMeasurementTick) {
+ trimIrrelevantData(currTick);
+
+ values.addLast(new Measurement(currTick, value));
+ isCachedAverageValid = false;
+ return;
+ }
+ }
+
+ public double getAverage() {
+
+ if (values.isEmpty()) return 0;
+
+ final int currTick = getWorldTimeInTicks();
+
+ Measurement lastMeasurement = values.peekLast();
+ final int lastMeasurementTick = lastMeasurement.TimestampInWorldTicks;
+
+ if (currTick < lastMeasurementTick) return 0;
+
+ if (currTick > lastMeasurementTick) {
+ trimIrrelevantData(currTick);
+ }
+
+ if (isCachedAverageValid) return cachedAverage;
+
+ return calculateAverage();
+ }
+
+ public long getLast() {
+
+ if (values.isEmpty()) return 0;
+
+ final int currTick = getWorldTimeInTicks();
+
+ Measurement lastMeasurement = values.peekLast();
+ final int lastMeasurementTick = lastMeasurement.TimestampInWorldTicks;
+
+ if (currTick == lastMeasurementTick) return values.getLast().Value;
+
+ return 0;
+ }
+
+ private double calculateAverage() {
+
+ isCachedAverageValid = true;
+ long sum = 0;
+
+ for (Measurement measurement : values) {
+ sum += measurement.Value;
+ }
+
+ return sum / (double) period;
+ }
+
+ private void trimIrrelevantData(int currWorldTimeInTicks) {
+
+ if (values.isEmpty()) return;
+
+ int firstMeasurementTick = values.peekFirst().TimestampInWorldTicks;
+
+ while (currWorldTimeInTicks - firstMeasurementTick >= period) {
+ values.removeFirst();
+ isCachedAverageValid = false;
+
+ if (values.isEmpty()) return;
+
+ firstMeasurementTick = values.peekFirst().TimestampInWorldTicks;
+ }
+ }
+
+ private int getWorldTimeInTicks() {
+ return MinecraftServer.getServer()
+ .getTickCounter();
+ }
+
+ private ArrayDeque<Measurement> values;
+ private int period;
+
+ private double cachedAverage = 0;
+ private boolean isCachedAverageValid = true;
+
+ private class Measurement {
+
+ public int TimestampInWorldTicks;
+ public long Value;
+
+ public Measurement(int timestampInWorldTicks, long value) {
+ this.TimestampInWorldTicks = timestampInWorldTicks;
+ this.Value = value;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/ColorsMetadataSection.java b/src/main/java/gregtech/api/util/ColorsMetadataSection.java
new file mode 100644
index 0000000000..fb9cc6dd56
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ColorsMetadataSection.java
@@ -0,0 +1,63 @@
+package gregtech.api.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.minecraft.client.resources.data.IMetadataSection;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+@SideOnly(Side.CLIENT)
+public class ColorsMetadataSection implements IMetadataSection {
+
+ private final Map<String, Integer> textColors;
+ private final Map<String, String> hexTextColors;
+ private final Map<String, Integer> guiTints;
+ private final Map<String, String> hexGuiTints;
+ private final boolean guiTintEnabled;
+
+ public ColorsMetadataSection(Map<String, String> hexTextColorMap, Map<String, String> hexGuiTintMap,
+ boolean guiTintEnabled) {
+ this.hexTextColors = hexTextColorMap;
+ this.textColors = convertHexMapToIntMap(hexTextColorMap);
+
+ this.hexGuiTints = hexGuiTintMap;
+ this.guiTints = convertHexMapToIntMap(hexGuiTintMap);
+
+ this.guiTintEnabled = guiTintEnabled;
+ }
+
+ private Map<String, Integer> convertHexMapToIntMap(Map<String, String> hexMap) {
+ Map<String, Integer> intMap = new HashMap<>();
+
+ for (String key : hexMap.keySet()) {
+ int colorValue = -1;
+ String hex = hexMap.get(key);
+ try {
+ if (!hex.isEmpty()) colorValue = Integer.parseUnsignedInt(hex, 16);
+ } catch (final NumberFormatException e) {
+ GT_Log.err.println("Couldn't format color correctly of " + key + " -> " + hex);
+ }
+ intMap.put(key, colorValue);
+ }
+ return intMap;
+ }
+
+ public int getTextColorOrDefault(String key, int defaultColor) {
+ return isColorInMap(key, this.hexTextColors) ? this.textColors.get(key) : defaultColor;
+ }
+
+ public int getGuiTintOrDefault(String key, int defaultColor) {
+ return isColorInMap(key, this.hexGuiTints) ? this.guiTints.get(key) : defaultColor;
+ }
+
+ private boolean isColorInMap(String key, Map<String, String> hexMap) {
+ return hexMap.containsKey(key) && !hexMap.get(key)
+ .isEmpty();
+ }
+
+ public boolean sGuiTintingEnabled() {
+ return this.guiTintEnabled;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/ColorsMetadataSectionSerializer.java b/src/main/java/gregtech/api/util/ColorsMetadataSectionSerializer.java
new file mode 100644
index 0000000000..389662d041
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ColorsMetadataSectionSerializer.java
@@ -0,0 +1,81 @@
+package gregtech.api.util;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.minecraft.client.resources.data.BaseMetadataSectionSerializer;
+import net.minecraft.util.JsonUtils;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.GT_Mod;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.Dyes;
+
+@SideOnly(Side.CLIENT)
+public class ColorsMetadataSectionSerializer extends BaseMetadataSectionSerializer {
+
+ public ColorsMetadataSection deserialize(JsonElement metadataColors, Type type,
+ JsonDeserializationContext context) {
+ // Default values
+ boolean enableGuiTint = GregTech_API.sColoredGUI;
+ Map<String, String> hexGuiTintMap = new HashMap<>();
+ Map<String, String> hexTextColorMap = new HashMap<>();
+
+ JsonObject jsonObject = JsonUtils.getJsonElementAsJsonObject(metadataColors, "metadata section");
+ if (jsonObject.has("textColor")) {
+ JsonObject textColors = JsonUtils.func_152754_s(jsonObject, "textColor");
+ for (Map.Entry<String, JsonElement> entry : textColors.entrySet()) {
+ if (entry.getValue()
+ .isJsonPrimitive()) {
+ hexTextColorMap.put(
+ entry.getKey(),
+ entry.getValue()
+ .getAsString());
+ } else {
+ GT_Mod.GT_FML_LOGGER.warn("ColorOverride expects primitive value for key `textColor`");
+ }
+ }
+ }
+
+ if (jsonObject.has("guiTint")) {
+ JsonObject guiTints = JsonUtils.func_152754_s(jsonObject, "guiTint");
+ enableGuiTint = JsonUtils
+ .getJsonObjectBooleanFieldValueOrDefault(guiTints, "enableGuiTintWhenPainted", true);
+
+ for (Dyes dye : Dyes.values()) {
+ hexGuiTintMap.put(dye.mName, GT_Util.toHexString(dye.getRGBA()));
+ }
+
+ for (String key : hexGuiTintMap.keySet()) {
+ if (enableGuiTint) {
+ hexGuiTintMap.replace(
+ key,
+ JsonUtils.getJsonObjectStringFieldValueOrDefault(guiTints, key, hexGuiTintMap.get(key)));
+ } else {
+ hexGuiTintMap.replace(key, GT_Util.toHexString(Dyes.dyeWhite.getRGBA()));
+ }
+ }
+ }
+
+ return new ColorsMetadataSection(hexTextColorMap, hexGuiTintMap, enableGuiTint);
+ }
+
+ public JsonElement serialize(ColorsMetadataSection colorsMetaSection, Type type, JsonSerializationContext context) {
+ return new JsonObject();
+ }
+
+ public String getSectionName() {
+ return "colors";
+ }
+
+ public JsonElement serialize(Object object, Type type, JsonSerializationContext context) {
+ return this.serialize((ColorsMetadataSection) object, type, context);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/ExternalMaterials.java b/src/main/java/gregtech/api/util/ExternalMaterials.java
new file mode 100644
index 0000000000..29564db2fd
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ExternalMaterials.java
@@ -0,0 +1,14 @@
+package gregtech.api.util;
+
+import gregtech.api.enums.Materials;
+
+public class ExternalMaterials {
+
+ public static Materials getRhodiumPlatedPalladium() {
+ return Materials.getWithFallback("Rhodium-PlatedPalladium", Materials.Chrome);
+ }
+
+ public static Materials getRuridit() {
+ return Materials.getWithFallback("Ruridit", Materials.Osmiridium);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/FieldsAreNonnullByDefault.java b/src/main/java/gregtech/api/util/FieldsAreNonnullByDefault.java
new file mode 100644
index 0000000000..1f51aa39a7
--- /dev/null
+++ b/src/main/java/gregtech/api/util/FieldsAreNonnullByDefault.java
@@ -0,0 +1,18 @@
+package gregtech.api.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.Nonnull;
+import javax.annotation.meta.TypeQualifierDefault;
+
+/**
+ * This annotation can be applied to a package or class to indicate that
+ * the fields in that element are nonnull by default unless there is an explicit nullness annotation.
+ * </ul>
+ */
+@Nonnull
+@TypeQualifierDefault({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FieldsAreNonnullByDefault {}
diff --git a/src/main/java/gregtech/api/util/FishPondFakeRecipe.java b/src/main/java/gregtech/api/util/FishPondFakeRecipe.java
new file mode 100644
index 0000000000..dc579ebd9b
--- /dev/null
+++ b/src/main/java/gregtech/api/util/FishPondFakeRecipe.java
@@ -0,0 +1,80 @@
+package gregtech.api.util;
+
+import java.util.ArrayList;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.WeightedRandomFishable;
+import net.minecraftforge.common.FishingHooks;
+import net.minecraftforge.fluids.FluidStack;
+
+import gtPlusPlus.api.objects.Logger;
+import gtPlusPlus.api.objects.data.AutoMap;
+import gtPlusPlus.api.recipe.GTPPRecipeMaps;
+import gtPlusPlus.core.recipe.common.CI;
+import gtPlusPlus.core.util.minecraft.ItemUtils;
+import gtPlusPlus.core.util.reflect.ReflectionUtils;
+
+public class FishPondFakeRecipe {
+
+ public static ArrayList<WeightedRandomFishable> fish = new ArrayList<>();
+ public static ArrayList<WeightedRandomFishable> junk = new ArrayList<>();
+ public static ArrayList<WeightedRandomFishable> treasure = new ArrayList<>();
+
+ @SuppressWarnings("unchecked")
+ public static boolean generateFishPondRecipes() {
+
+ try {
+ fish = (ArrayList<WeightedRandomFishable>) ReflectionUtils.getField(FishingHooks.class, "fish")
+ .get(null);
+ junk = (ArrayList<WeightedRandomFishable>) ReflectionUtils.getField(FishingHooks.class, "junk")
+ .get(null);
+ treasure = (ArrayList<WeightedRandomFishable>) ReflectionUtils.getField(FishingHooks.class, "treasure")
+ .get(null);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ Logger.INFO("Error generating Fish Pond Recipes. [1]");
+ e.printStackTrace();
+ }
+
+ AutoMap<ArrayList<WeightedRandomFishable>> mega = new AutoMap<>();
+ mega.put(fish);
+ mega.put(junk);
+ mega.put(treasure);
+
+ int mType = 14;
+ for (ArrayList<WeightedRandomFishable> f : mega.values()) {
+ for (WeightedRandomFishable weightedRandomFishable : f) {
+ if (weightedRandomFishable != null) {
+ WeightedRandomFishable u = weightedRandomFishable;
+ try {
+ ItemStack t = (ItemStack) ReflectionUtils
+ .getField(WeightedRandomFishable.class, "field_150711_b")
+ .get(u);
+ addNewFishPondLoot(mType, new ItemStack[] { t }, new int[] { 10000 });
+ } catch (IllegalArgumentException | IllegalAccessException e1) {
+ Logger.INFO("Error generating Fish Pond Recipes. [2]");
+ e1.printStackTrace();
+ }
+ }
+ }
+ mType++;
+ }
+
+ return true;
+ }
+
+ public static void addNewFishPondLoot(int circuit, ItemStack[] outputItems, int[] chances) {
+ GT_Recipe x = new GT_Recipe(
+ true,
+ new ItemStack[] { CI.getNumberedCircuit(circuit) },
+ outputItems,
+ null,
+ chances,
+ new FluidStack[] { null },
+ new FluidStack[] { null },
+ 100, // 1 Tick
+ 0, // No Eu produced
+ 0);
+ Logger.INFO("Fishing [" + circuit + "]: " + ItemUtils.getArrayStackNames(outputItems));
+ GTPPRecipeMaps.fishPondRecipes.addRecipe(x, false, false, false);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ApiaryModifier.java b/src/main/java/gregtech/api/util/GT_ApiaryModifier.java
new file mode 100644
index 0000000000..4a89345670
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ApiaryModifier.java
@@ -0,0 +1,24 @@
+package gregtech.api.util;
+
+import net.minecraft.world.biome.BiomeGenBase;
+
+public class GT_ApiaryModifier {
+
+ public float territory = 1f;
+ public float mutation = 1f;
+ public float lifespan = 1f;
+ public float production = 2f;
+ public float flowering = 1f;
+ public float geneticDecay = 1f;
+ public boolean isSealed = false;
+ public boolean isSelfLighted = false;
+ public boolean isSelfUnlighted = false;
+ public boolean isSunlightSimulated = false;
+ public boolean isAutomated = false;
+ public boolean isCollectingPollen = false;
+ public BiomeGenBase biomeOverride = null;
+ public float energy = 1f;
+ public float temperature = 0f;
+ public float humidity = 0f;
+ public int maxSpeed = 0;
+}
diff --git a/src/main/java/gregtech/api/util/GT_ApiaryUpgrade.java b/src/main/java/gregtech/api/util/GT_ApiaryUpgrade.java
new file mode 100644
index 0000000000..9ed7a56e1e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ApiaryUpgrade.java
@@ -0,0 +1,225 @@
+package gregtech.api.util;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.biome.BiomeGenBase;
+
+import gregtech.api.enums.OrePrefixes;
+import gregtech.common.items.GT_MetaGenerated_Item_03;
+
+/**
+ * Actual items are defined in {@link GT_MetaGenerated_Item_03}
+ */
+public enum GT_ApiaryUpgrade {
+
+ speed1(UNIQUE_INDEX.SPEED_UPGRADE, 32200, 1, (mods, n) -> mods.maxSpeed = 1),
+ speed2(UNIQUE_INDEX.SPEED_UPGRADE, 32201, 1, (mods, n) -> mods.maxSpeed = 2),
+ speed3(UNIQUE_INDEX.SPEED_UPGRADE, 32202, 1, (mods, n) -> mods.maxSpeed = 3),
+ speed4(UNIQUE_INDEX.SPEED_UPGRADE, 32203, 1, (mods, n) -> mods.maxSpeed = 4),
+ speed5(UNIQUE_INDEX.SPEED_UPGRADE, 32204, 1, (mods, n) -> mods.maxSpeed = 5),
+ speed6(UNIQUE_INDEX.SPEED_UPGRADE, 32205, 1, (mods, n) -> mods.maxSpeed = 6),
+ speed7(UNIQUE_INDEX.SPEED_UPGRADE, 32206, 1, (mods, n) -> mods.maxSpeed = 7),
+ speed8(UNIQUE_INDEX.SPEED_UPGRADE, 32207, 1, (mods, n) -> mods.maxSpeed = 8),
+ speed8upgraded(UNIQUE_INDEX.SPEED_UPGRADE, 32208, 1, (mods, n) -> {
+ mods.maxSpeed = 8;
+ mods.production = 17.19926784f;
+ mods.energy *= 14.75;
+ }),
+ production(UNIQUE_INDEX.PRODUCTION_UPGRADE, 32209, 8, (mods, n) -> {
+ mods.production = 4.f * (float) Math.pow(1.2d, n);
+ mods.energy *= Math.pow(1.4f, n);
+ }),
+ plains(UNIQUE_INDEX.PLAINS_UPGRADE, 32210, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.plains;
+ mods.energy *= 1.2f;
+ }),
+ light(UNIQUE_INDEX.LIGHT_UPGRADE, 32211, 1, (mods, n) -> {
+ mods.isSelfLighted = true;
+ mods.energy *= 1.05f;
+ }),
+ flowering(UNIQUE_INDEX.FLOWERING_UPGRADE, 32212, 8, (mods, n) -> {
+ mods.flowering *= Math.pow(1.2f, n);
+ mods.energy *= Math.pow(1.1f, n);
+ }),
+ winter(UNIQUE_INDEX.WINTER_UPGRADE, 32213, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.taiga;
+ mods.energy *= 1.5f;
+ }),
+ dryer(UNIQUE_INDEX.DRYER_UPGRADE, 32214, 16, (mods, n) -> {
+ mods.humidity -= 0.125f * n;
+ mods.energy *= Math.pow(1.025f, n);
+ }),
+ automation(UNIQUE_INDEX.AUTOMATION_UPGRADE, 32215, 1, (mods, n) -> {
+ mods.isAutomated = true;
+ mods.energy *= 1.1f;
+ }),
+ humidifier(UNIQUE_INDEX.HUMIDIFIER_UPGRADE, 32216, 16, (mods, n) -> {
+ mods.humidity += 0.125f * n;
+ mods.energy *= Math.pow(1.05f, n);
+ }),
+ hell(UNIQUE_INDEX.HELL_UPGRADE, 32217, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.hell;
+ mods.energy *= 1.5f;
+ }),
+ pollen(UNIQUE_INDEX.POLLEN_UPGRADE, 32218, 1, (mods, n) -> {
+ mods.flowering = 0f;
+ mods.energy *= 1.3f;
+ }),
+ desert(UNIQUE_INDEX.DESERT_UPGRADE, 32219, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.desert;
+ mods.energy *= 1.2f;
+ }),
+ cooler(UNIQUE_INDEX.COOLER_UPGRADE, 32220, 16, (mods, n) -> {
+ mods.temperature -= 0.125f * n;
+ mods.energy *= Math.pow(1.025f, n);
+ }),
+ lifespan(UNIQUE_INDEX.LIFESPAN_UPGRADE, 32221, 4, (mods, n) -> {
+ mods.lifespan /= Math.pow(1.5f, n);
+ mods.energy *= Math.pow(1.05f, n);
+ }),
+ seal(UNIQUE_INDEX.SEAL_UPGRADE, 32222, 1, (mods, n) -> {
+ mods.isSealed = true;
+ mods.energy *= 1.05f;
+ }),
+ stabilizer(UNIQUE_INDEX.STABILIZER_UPGRADE, 32223, 1, (mods, n) -> {
+ mods.geneticDecay = 0f;
+ mods.energy *= 2.50f;
+ }),
+ jungle(UNIQUE_INDEX.JUNGLE_UPGRADE, 32224, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.jungle;
+ mods.energy *= 1.20f;
+ }),
+ territory(UNIQUE_INDEX.TERRITORY_UPGRADE, 32225, 4, (mods, n) -> {
+ mods.territory *= Math.pow(1.5f, n);
+ mods.energy *= Math.pow(1.05f, n);
+ }),
+ ocean(UNIQUE_INDEX.OCEAN_UPGRADE, 32226, 1, (mods, n) -> {
+ mods.biomeOverride = BiomeGenBase.ocean;
+ mods.energy *= 1.20f;
+ }),
+ sky(UNIQUE_INDEX.SKY_UPGRADE, 32227, 1, (mods, n) -> {
+ mods.isSunlightSimulated = true;
+ mods.energy *= 1.05f;
+ }),
+ heater(UNIQUE_INDEX.HEATER_UPGRADE, 32228, 16, (mods, n) -> {
+ mods.temperature += 0.125f * n;
+ mods.energy *= Math.pow(1.025f, n);
+ }),
+ sieve(UNIQUE_INDEX.SIEVE_UPGRADE, 32229, 1, (mods, n) -> {
+ mods.isCollectingPollen = true;
+ mods.energy *= 1.05f;
+ }),
+ unlight(UNIQUE_INDEX.LIGHT_UPGRADE, 32231, 1, (mods, n) -> {
+ mods.isSelfUnlighted = true;
+ mods.energy *= 1.05f;
+ }),;
+
+ private enum UNIQUE_INDEX {
+
+ SPEED_UPGRADE,
+ PRODUCTION_UPGRADE,
+ PLAINS_UPGRADE,
+ LIGHT_UPGRADE, // also unlight
+ FLOWERING_UPGRADE,
+ WINTER_UPGRADE,
+ DRYER_UPGRADE,
+ AUTOMATION_UPGRADE,
+ HUMIDIFIER_UPGRADE,
+ HELL_UPGRADE,
+ POLLEN_UPGRADE,
+ DESERT_UPGRADE,
+ COOLER_UPGRADE,
+ LIFESPAN_UPGRADE,
+ SEAL_UPGRADE,
+ STABILIZER_UPGRADE,
+ JUNGLE_UPGRADE,
+ TERRITORY_UPGRADE,
+ OCEAN_UPGRADE,
+ SKY_UPGRADE,
+ HEATER_UPGRADE,
+ SIEVE_UPGRADE,;
+
+ void apply(Consumer<GT_ApiaryUpgrade> fn) {
+ UNIQUE_UPGRADE_LIST.get(this)
+ .forEach(fn);
+ }
+ }
+
+ private static final EnumMap<UNIQUE_INDEX, ArrayList<GT_ApiaryUpgrade>> UNIQUE_UPGRADE_LIST = new EnumMap<>(
+ UNIQUE_INDEX.class);
+
+ private int meta = 0;
+ private int maxnumber = 1;
+
+ private final GT_Utility.ItemId id;
+ private final UNIQUE_INDEX unique_index;
+ private final BiConsumer<GT_ApiaryModifier, Integer> applier;
+
+ private final HashSet<GT_Utility.ItemId> blacklistedUpgrades = new HashSet<>();
+
+ GT_ApiaryUpgrade(UNIQUE_INDEX unique_index, int meta, int maxnumber,
+ BiConsumer<GT_ApiaryModifier, Integer> applier) {
+ this.unique_index = unique_index;
+ this.meta = meta;
+ this.maxnumber = maxnumber;
+ this.applier = applier;
+ this.id = GT_Utility.ItemId.createNoCopy(get(1));
+ }
+
+ private void setup_static_variables() {
+ quickLookup.put(this.meta, this);
+ ArrayList<GT_ApiaryUpgrade> un = UNIQUE_UPGRADE_LIST.get(this.unique_index);
+ if (un != null) un.forEach((u) -> {
+ u.blacklistedUpgrades.add(this.id);
+ this.blacklistedUpgrades.add(u.id);
+ });
+ else {
+ un = new ArrayList<>(1);
+ UNIQUE_UPGRADE_LIST.put(this.unique_index, un);
+ }
+ un.add(this);
+ }
+
+ public static GT_ApiaryUpgrade getUpgrade(ItemStack s) {
+ if (s == null) return null;
+ if (!isUpgrade(s)) return null;
+ return quickLookup.get(s.getItemDamage());
+ }
+
+ public boolean isAllowedToWorkWith(ItemStack s) {
+ GT_Utility.ItemId id = GT_Utility.ItemId.createNoCopy(s);
+ return !blacklistedUpgrades.contains(id);
+ }
+
+ public int getMaxNumber() {
+ return maxnumber;
+ }
+
+ public void applyModifiers(GT_ApiaryModifier mods, ItemStack stack) {
+ if (applier != null) applier.accept(mods, stack.stackSize);
+ }
+
+ public ItemStack get(int count) {
+ return new ItemStack(GT_MetaGenerated_Item_03.INSTANCE, count, meta);
+ }
+
+ public static boolean isUpgrade(ItemStack s) {
+ return OrePrefixes.apiaryUpgrade.contains(s);
+ }
+
+ private static final HashMap<Integer, GT_ApiaryUpgrade> quickLookup = new HashMap<>();
+
+ static {
+ EnumSet.allOf(GT_ApiaryUpgrade.class)
+ .forEach(GT_ApiaryUpgrade::setup_static_variables);
+ speed8upgraded.blacklistedUpgrades.add(production.id);
+ production.blacklistedUpgrades.add(speed8upgraded.id);
+ }
+}
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..1ada9c78d5
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_AssemblyLineUtils.java
@@ -0,0 +1,557 @@
+package gregtech.api.util;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.nbt.NBTTagString;
+import net.minecraftforge.common.util.Constants.NBT;
+import net.minecraftforge.fluids.FluidStack;
+
+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;
+
+public class GT_AssemblyLineUtils {
+
+ /**
+ * A cache of Recipes using the Output as Key.
+ */
+ private static final HashMap<GT_ItemStack, GT_Recipe_AssemblyLine> sRecipeCacheByOutput = new HashMap<>();
+ /**
+ * A cache of Recipes using the Recipe Hash String as Key.
+ */
+ private static final HashMap<String, GT_Recipe_AssemblyLine> sRecipeCacheByRecipeHash = new HashMap<>();
+
+ /**
+ * 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 GT_Recipe_AssemblyLine processDataStick(ItemStack aDataStick) {
+ if (!isItemDataStick(aDataStick)) {
+ return null;
+ }
+ if (doesDataStickNeedUpdate(aDataStick)) {
+ ItemStack aStickOutput = getDataStickOutput(aDataStick);
+ if (aStickOutput != null) {
+ GT_Recipe_AssemblyLine aIntendedRecipe = findAssemblyLineRecipeByOutput(aStickOutput);
+ if (aIntendedRecipe != null && setAssemblyLineRecipeOnDataStick(aDataStick, aIntendedRecipe))
+ return aIntendedRecipe;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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).getRecipe();
+ }
+
+ /**
+ * 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.
+ */
+ @Nonnull
+ public static LookupResult findAssemblyLineRecipeFromDataStick(ItemStack aDataStick, boolean aReturnBuiltRecipe) {
+ if (!isItemDataStick(aDataStick) || !doesDataStickHaveOutput(aDataStick)) {
+ return LookupResultType.INVALID_STICK.getResult();
+ }
+ List<ItemStack> aInputs = new ArrayList<>(16);
+ ItemStack aOutput = getDataStickOutput(aDataStick);
+ List<List<ItemStack>> mOreDictAlt = new ArrayList<>(16);
+ List<FluidStack> aFluidInputs = new ArrayList<>(4);
+
+ NBTTagCompound aTag = aDataStick.getTagCompound();
+ if (aTag == null) {
+ return LookupResultType.INVALID_STICK.getResult();
+ }
+
+ // Get From Cache
+ if (doesDataStickHaveRecipeHash(aDataStick)) {
+ GT_Recipe_AssemblyLine aRecipeFromCache = sRecipeCacheByRecipeHash.get(getHashFromDataStack(aDataStick));
+ if (aRecipeFromCache != null && GT_Utility.areStacksEqual(aOutput, aRecipeFromCache.mOutput)) {
+ return LookupResultType.VALID_STACK_AND_VALID_HASH.getResult(aRecipeFromCache);
+ } // else: no cache, or the old recipe run into a hash collision with a different new recipe
+ }
+
+ for (int i = 0; i < 16; i++) {
+ int count = aTag.getInteger("a" + i);
+ if (!aTag.hasKey("" + i) && count <= 0) {
+ continue;
+ }
+
+ List<ItemStack> tAltCurrent = new ArrayList<>();
+ for (int j = 0; j < count; j++) {
+ ItemStack tLoaded = GT_Utility.loadItem(aTag, "a" + i + ":" + j);
+ if (tLoaded == null) {
+ continue;
+ }
+ tAltCurrent.add(tLoaded);
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Item Alt " + i + " : " + tLoaded.getUnlocalizedName());
+ }
+ }
+ mOreDictAlt.add(tAltCurrent);
+ ItemStack tLoaded = GT_Utility.loadItem(aTag, "" + i);
+ if (tLoaded == null) {
+ continue;
+ }
+ aInputs.add(tLoaded);
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Item " + i + " : " + tLoaded.getUnlocalizedName());
+ }
+ }
+
+ 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;
+ FluidStack tLoaded = GT_Utility.loadFluid(aTag, "f" + i);
+ if (tLoaded == null) continue;
+ aFluidInputs.add(tLoaded);
+ if (GT_Values.D1) {
+ GT_FML_LOGGER.info("Fluid " + i + " " + tLoaded.getUnlocalizedName());
+ }
+ }
+ if (!aTag.hasKey("output") || !aTag.hasKey("time")
+ || aTag.getInteger("time") <= 0
+ || !aTag.hasKey("eu")
+ || !GT_Utility.isStackValid(aOutput)) {
+ return LookupResultType.INVALID_STICK.getResult();
+ }
+ 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) {
+ return LookupResultType.VALID_STACK_AND_VALID_HASH.getResult(
+ new GT_Recipe_AssemblyLine(
+ null,
+ 0,
+ aInputs.toArray(new ItemStack[0]),
+ aFluidInputs.toArray(new FluidStack[0]),
+ aOutput,
+ aTime,
+ aEU));
+ }
+
+ for (GT_Recipe_AssemblyLine aRecipe : GT_Recipe.GT_Recipe_AssemblyLine.sAssemblylineRecipes) {
+ if (aRecipe.mEUt != aEU || aRecipe.mDuration != aTime) continue;
+ if (!GT_Utility.areStacksEqual(aOutput, aRecipe.mOutput, true)) continue;
+ if (!GT_Utility.areStackListsEqual(Arrays.asList(aRecipe.mInputs), aInputs, false, true)) continue;
+ if (!Objects.equals(Arrays.asList(aRecipe.mFluidInputs), aFluidInputs)) continue;
+ if (!areStacksEqual(aRecipe.mOreDictAlt, mOreDictAlt)) continue;
+
+ // Cache it
+ String aRecipeHash = generateRecipeHash(aRecipe);
+ sRecipeCacheByRecipeHash.put(aRecipeHash, aRecipe);
+ sRecipeCacheByOutput.put(new GT_ItemStack(aRecipe.mOutput), aRecipe);
+ if (doesDataStickHaveRecipeHash(aDataStick)) {
+ String aStickHash = getHashFromDataStack(aDataStick);
+ if (aRecipeHash.equals(aStickHash))
+ return LookupResultType.VALID_STACK_AND_VALID_HASH.getResult(aRecipe);
+ }
+ return LookupResultType.VALID_STACK_AND_VALID_RECIPE.getResult(aRecipe);
+ }
+ return LookupResultType.VALID_STACK_BUT_INVALID_RECIPE.getResult();
+ }
+
+ private static boolean areStacksEqual(ItemStack[][] lhs, List<List<ItemStack>> rhs) {
+ for (int i = 0; i < lhs.length; i++) {
+ if (!areStacksEqual(lhs[i], rhs.get(i))) return false;
+ }
+ return true;
+ }
+
+ private static boolean areStacksEqual(ItemStack[] lhs, List<ItemStack> rhs) {
+ return lhs == null ? rhs.isEmpty()
+ : !rhs.isEmpty() && GT_Utility.areStackListsEqual(Arrays.asList(lhs), rhs, false, true);
+ }
+
+ /**
+ * 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.getPersistentHash();
+ }
+ return aHash;
+ }
+
+ /**
+ * @param aRecipe - The recipe to add to internal caches
+ * @throws IllegalArgumentException if given recipe collide with any existing recipe in the cache
+ */
+ public static void addRecipeToCache(GT_Recipe_AssemblyLine aRecipe) {
+ if (aRecipe != null) {
+ String aHash = "Hash." + aRecipe.getPersistentHash();
+ GT_Recipe_AssemblyLine existing = sRecipeCacheByOutput.put(new GT_ItemStack(aRecipe.mOutput), aRecipe);
+ if (existing != null) throw new IllegalArgumentException("Duplicate assline recipe for " + aRecipe.mOutput);
+ existing = sRecipeCacheByRecipeHash.put(aHash, aRecipe);
+ if (existing != null && !existing.equals(aRecipe))
+ throw new IllegalArgumentException("Recipe hash collision for " + aRecipe + " and " + existing);
+ }
+ }
+
+ /**
+ * @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) {
+ // persistent hash can never be 0
+ return !aHash.equals("Invalid.Recipe.Hash") && !aHash.equals("Hash.0");
+ }
+ 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) {
+ return isItemDataStick(aDataStick) && aDataStick.hasTagCompound()
+ && aDataStick.getTagCompound()
+ .hasKey("output");
+ }
+
+ /**
+ * @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);
+ return !aStickHash.equals(generateRecipeHash(aIntendedRecipe));
+ }
+ }
+ 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();
+ return aNBT.hasKey("Data.Recipe.Hash") && !aNBT.getString("Data.Recipe.Hash")
+ .equals("Hash.0");
+ }
+ 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)) {
+ return GT_Utility.loadItem(aDataStick.getTagCompound(), "output");
+ }
+ return null;
+ }
+
+ /**
+ * @param aDataStick - The Data Stick to process.
+ * @return The stored Recipe Hash String on the Data Stick, will return an invalid Hash if one is not found.
+ * <p>
+ * The hash will be guaranteed to pass 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", NBT.TAG_STRING)) {
+ String hash = aNBT.getString("Data.Recipe.Hash");
+ if (isValidHash(hash)) return 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).recipe;
+ GT_FML_LOGGER.info(
+ "Updating data stick: " + aDataStick.getDisplayName()
+ + " | Old Recipe Hash: "
+ + generateRecipeHash(aOldRecipe)
+ + ", New Recipe Hash: "
+ + aHash);
+ }
+
+ String author = "Assembling Line Recipe Generator";
+ String displayName = null;
+ if (aDataStick.hasTagCompound()) {
+ NBTTagCompound tag = aDataStick.getTagCompound();
+ if (tag.hasKey("author", NBT.TAG_STRING)) {
+ author = tag.getString("author");
+ }
+ if (tag.hasKey("display", NBT.TAG_COMPOUND)) {
+ NBTTagCompound displayTag = tag.getCompoundTag("display");
+ if (displayTag.hasKey("Name", NBT.TAG_STRING)) displayName = displayTag.getString("Name");
+ }
+ }
+
+ // remove possible old NBTTagCompound
+ aDataStick.setTagCompound(new NBTTagCompound());
+ if (displayName != null) aDataStick.setStackDisplayName(displayName);
+ 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", author);
+ 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);
+ tNBT.setLong("lastUpdate", System.currentTimeMillis());
+ aDataStick.setTagCompound(tNBT);
+ // Set recipe hash
+ setRecipeHashOnDataStick(aDataStick, aHash);
+ return true;
+ }
+ return false;
+ }
+
+ public enum LookupResultType {
+
+ INVALID_STICK(true),
+ VALID_STACK_BUT_INVALID_RECIPE(true),
+ VALID_STACK_AND_VALID_RECIPE(false),
+ VALID_STACK_AND_VALID_HASH(false);
+
+ private final boolean recipeNull;
+ private LookupResult singletonResult;
+
+ LookupResultType(boolean recipeNull) {
+ this.recipeNull = recipeNull;
+ }
+
+ public LookupResult getResult() {
+ if (!recipeNull) throw new IllegalArgumentException("This result type require a nonnull recipe");
+ if (singletonResult == null) singletonResult = new LookupResult(null, this);
+ return singletonResult;
+ }
+
+ public LookupResult getResult(GT_Recipe_AssemblyLine recipe) {
+ if ((recipe == null) != recipeNull)
+ throw new IllegalArgumentException("This result type does not allow given input");
+ return new LookupResult(recipe, this);
+ }
+ }
+
+ public static class LookupResult {
+
+ private final GT_Recipe_AssemblyLine recipe;
+ private final LookupResultType type;
+
+ LookupResult(GT_Recipe_AssemblyLine recipe, LookupResultType type) {
+ this.recipe = recipe;
+ this.type = type;
+ }
+
+ public GT_Recipe_AssemblyLine getRecipe() {
+ return recipe;
+ }
+
+ public LookupResultType getType() {
+ return type;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Assemblyline_Server.java b/src/main/java/gregtech/api/util/GT_Assemblyline_Server.java
new file mode 100644
index 0000000000..4c0348683a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Assemblyline_Server.java
@@ -0,0 +1,297 @@
+package gregtech.api.util;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import net.minecraftforge.common.config.ConfigCategory;
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.common.config.Property;
+
+import cpw.mods.fml.common.event.FMLPreInitializationEvent;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.MaterialsBotania;
+
+public class GT_Assemblyline_Server {
+
+ public static LinkedHashMap<String, String> lServerNames = new LinkedHashMap<>();
+ private static LinkedHashMap<String, String> internal2 = new LinkedHashMap<>(), internal3 = new LinkedHashMap<>(),
+ internal4 = new LinkedHashMap<>();
+ private static HashMap<String, Property> internal = new HashMap<>();
+
+ public static void fillMap(FMLPreInitializationEvent aEvent) {
+ Configuration conf = GT_LanguageManager.sEnglishFile;
+
+ ConfigCategory cat = conf.getCategory("languagefile");
+ internal.putAll(cat.getValues());
+ for (Map.Entry<String, Property> entry : internal.entrySet()) {
+ try {
+ String s = entry.getValue()
+ .getString()
+ .replaceAll("%", "");
+
+ if (entry.getKey()
+ .contains("metaitem") && s.contains("material")) internal2.put(entry.getKey(), s);
+ else if (entry.getKey()
+ .contains("blockmachines") && s.contains("material")) internal3.put(entry.getKey(), s);
+ else if ((entry.getKey()
+ .contains("blockores")
+ || (entry.getKey()
+ .contains("blockmetal")
+ || entry.getKey()
+ .contains("blockgem")))
+ && s.contains("material")) internal4.put(entry.getKey(), s);
+ else lServerNames.put(entry.getKey(), s);
+ } catch (Exception ignored) {}
+ }
+ for (Map.Entry<String, String> entry : internal2.entrySet()) {
+ try {
+ if (entry.getKey()
+ .contains("name")) {
+ int i = Integer.parseInt(
+ entry.getKey()
+ .substring(
+ "gt.metaitem.01.".length(),
+ entry.getKey()
+ .length() - ".name".length()));
+ i = i % 1000;
+ if (GregTech_API.sGeneratedMaterials[i] != null) lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace("material", GregTech_API.sGeneratedMaterials[i].toString()));
+ else lServerNames.put(entry.getKey(), null);
+ }
+ } catch (Exception ignored) {}
+ }
+ for (Map.Entry<String, String> entry : internal3.entrySet()) {
+ try {
+ if (entry.getKey()
+ .contains("cable"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.cable.".length(),
+ entry.getKey()
+ .length() - ".01.name".length())));
+ else if (entry.getKey()
+ .contains("gt_frame_"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_frame_".length(),
+ entry.getKey()
+ .length() - ".name".length())));
+ else if (entry.getKey()
+ .contains("gt_pipe_")) {
+ if (!entry.getKey()
+ .contains("_huge")
+ && !entry.getKey()
+ .contains("_large")
+ && !entry.getKey()
+ .contains("_nonuple")
+ && !entry.getKey()
+ .contains("_quadruple")
+ && !entry.getKey()
+ .contains("_small")
+ && !entry.getKey()
+ .contains("_tiny"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_pipe_".length(),
+ entry.getKey()
+ .length() - ".name".length())));
+ else if (entry.getKey()
+ .contains("_huge")
+ || entry.getKey()
+ .contains("_tiny"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_pipe_".length(),
+ entry.getKey()
+ .length() - "_tiny.name".length())));
+ else if (entry.getKey()
+ .contains("_large")
+ || entry.getKey()
+ .contains("_small"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_pipe_".length(),
+ entry.getKey()
+ .length() - "_large.name".length())));
+ else if (entry.getKey()
+ .contains("_nonuple"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_pipe_".length(),
+ entry.getKey()
+ .length() - "_nonuple.name".length())));
+ else if (entry.getKey()
+ .contains("_quadruple"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.gt_pipe_".length(),
+ entry.getKey()
+ .length() - "_quadruple.name".length())));
+ } else if (entry.getKey()
+ .contains("wire"))
+ lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace(
+ "material",
+ entry.getKey()
+ .substring(
+ "gt.blockmachines.wire.".length(),
+ entry.getKey()
+ .length() - ".01.name".length())));
+ else lServerNames.put(entry.getKey(), entry.getValue());
+ } catch (Exception ignored) {}
+ }
+ for (Map.Entry<String, String> entry : internal4.entrySet()) {
+ try {
+ if (entry.getKey()
+ .contains("blockores")) {
+ int i = Integer.parseInt(
+ entry.getKey()
+ .substring(
+ "gt.blockores.".length(),
+ entry.getKey()
+ .length() - ".name".length()));
+ i = i % 1000;
+ if (GregTech_API.sGeneratedMaterials[i] != null) lServerNames.put(
+ entry.getKey(),
+ entry.getValue()
+ .replace("material", GregTech_API.sGeneratedMaterials[i].toString()));
+ else lServerNames.put(entry.getKey(), null);
+ } else if (entry.getKey()
+ .contains("blockmetal")) {
+ Materials[] mMats = null;
+ String t = entry.getKey()
+ .substring("gt.blockmetal".length());
+ t = t.substring(0, 1);
+ int i = Integer.parseInt(t);
+ switch (i) {
+ case 1 -> mMats = new Materials[] { Materials.Adamantium, Materials.Aluminium,
+ Materials.Americium, Materials.AnnealedCopper, Materials.Antimony, Materials.Arsenic,
+ Materials.AstralSilver, Materials.BatteryAlloy, Materials.Beryllium, Materials.Bismuth,
+ Materials.BismuthBronze, Materials.BlackBronze, Materials.BlackSteel,
+ Materials.BlueAlloy, Materials.BlueSteel, Materials.Brass };
+ case 2 -> mMats = new Materials[] { Materials.Bronze, Materials.Caesium, Materials.Cerium,
+ Materials.Chrome, Materials.ChromiumDioxide, Materials.Cobalt, Materials.CobaltBrass,
+ Materials.Copper, Materials.Cupronickel, Materials.DamascusSteel, Materials.DarkIron,
+ Materials.DeepIron, Materials.Desh, Materials.Duranium, Materials.Dysprosium,
+ Materials.Electrum };
+ case 3 -> mMats = new Materials[] { Materials.ElectrumFlux, Materials.Enderium,
+ Materials.Erbium, Materials.Europium, Materials.FierySteel, Materials.Gadolinium,
+ Materials.Gallium, Materials.Holmium, Materials.HSLA, Materials.Indium,
+ Materials.InfusedGold, Materials.Invar, Materials.Iridium, Materials.IronMagnetic,
+ Materials.IronWood, Materials.Kanthal };
+ case 4 -> mMats = new Materials[] { Materials.Knightmetal, Materials.Lanthanum,
+ Materials.Lead, Materials.Lutetium, Materials.Magnalium, Materials.Magnesium,
+ Materials.Manganese, Materials.MeteoricIron, Materials.MeteoricSteel, Materials.Trinium,
+ Materials.Mithril, Materials.Molybdenum, Materials.Naquadah, Materials.NaquadahAlloy,
+ Materials.NaquadahEnriched, Materials.Naquadria };
+ case 5 -> mMats = new Materials[] { Materials.Neodymium, Materials.NeodymiumMagnetic,
+ Materials.Neutronium, Materials.Nichrome, Materials.Nickel, Materials.Niobium,
+ Materials.NiobiumNitride, Materials.NiobiumTitanium, Materials.Osmiridium,
+ Materials.Osmium, Materials.Palladium, Materials.PigIron, Materials.Platinum,
+ Materials.Plutonium, Materials.Plutonium241, Materials.Praseodymium };
+ case 6 -> mMats = new Materials[] { Materials.Promethium, Materials.RedAlloy,
+ Materials.RedSteel, Materials.RoseGold, Materials.Rubidium, Materials.Samarium,
+ Materials.Scandium, Materials.ShadowIron, Materials.ShadowSteel, Materials.Silicon,
+ Materials.Silver, Materials.SolderingAlloy, Materials.StainlessSteel, Materials.Steel,
+ Materials.SteelMagnetic, Materials.SterlingSilver };
+ case 7 -> mMats = new Materials[] { Materials.Sunnarium, Materials.Tantalum,
+ Materials.Tellurium, Materials.Terbium, Materials.Thaumium, Materials.Thorium,
+ Materials.Thulium, Materials.Tin, Materials.TinAlloy, Materials.Titanium,
+ Materials.Tritanium, Materials.Tungsten, Materials.TungstenSteel, Materials.Ultimet,
+ Materials.Uranium, Materials.Uranium235 };
+ case 8 -> mMats = new Materials[] { Materials.Vanadium, Materials.VanadiumGallium,
+ Materials.WroughtIron, Materials.Ytterbium, Materials.Yttrium,
+ Materials.YttriumBariumCuprate, Materials.Zinc, Materials.TungstenCarbide,
+ Materials.VanadiumSteel, Materials.HSSG, Materials.HSSE, Materials.HSSS,
+ Materials.Steeleaf, Materials.Ichorium, Materials.Firestone };
+ }
+ t = entry.getKey()
+ .substring(
+ "gt.blockmetal1.".length(),
+ entry.getKey()
+ .length() - ".name".length());
+ i = Integer.parseInt(t);
+ lServerNames.put(entry.getKey(), "Block of " + mMats[i].toString());
+ mMats = null;
+ } else if (entry.getKey()
+ .contains("blockgem")) {
+ Materials[] mMats = null;
+ String t = entry.getKey()
+ .substring("gt.blockgem".length());
+ t = t.substring(0, 1);
+ int i = Integer.parseInt(t);
+ switch (i) {
+ case 1 -> mMats = new Materials[] { Materials.InfusedAir, Materials.Amber,
+ Materials.Amethyst, Materials.InfusedWater, Materials.BlueTopaz,
+ Materials.CertusQuartz, Materials.Dilithium, Materials.EnderEye,
+ Materials.EnderPearl, Materials.FoolsRuby, Materials.Force, Materials.Forcicium,
+ Materials.Forcillium, Materials.GreenSapphire, Materials.InfusedFire,
+ Materials.Jasper, MaterialsBotania.ManaDiamond,
+ MaterialsBotania.BotaniaDragonstone };
+ case 2 -> mMats = new Materials[] { Materials.Lazurite, Materials.Lignite,
+ Materials.Monazite, Materials.Niter, Materials.Olivine, Materials.Opal,
+ Materials.InfusedOrder, Materials.InfusedEntropy, Materials.Phosphorus,
+ Materials.Quartzite, Materials.GarnetRed, Materials.Ruby, Materials.Sapphire,
+ Materials.Sodalite, Materials.Tanzanite, Materials.InfusedEarth };
+ case 3 -> mMats = new Materials[] { Materials.Topaz, Materials.Vinteum,
+ Materials.GarnetYellow, Materials.NetherStar, Materials.Charcoal, Materials.Blaze };
+ }
+ t = entry.getKey()
+ .substring(
+ "gt.blockgem1.".length(),
+ entry.getKey()
+ .length() - ".name".length());
+ i = Integer.parseInt(t);
+ lServerNames.put(entry.getKey(), "Block of " + mMats[i].toString());
+ mMats = null;
+ }
+ } catch (Exception ignored) {}
+ }
+
+ internal = null;
+ internal2 = null;
+ internal3 = null;
+ internal4 = null;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_BaseCrop.java b/src/main/java/gregtech/api/util/GT_BaseCrop.java
new file mode 100644
index 0000000000..d8608c85a0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_BaseCrop.java
@@ -0,0 +1,311 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+import static gregtech.api.enums.Mods.IC2CropPlugin;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+
+import gregtech.GT_Mod;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.ConfigCategories;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.objects.ItemData;
+import gregtech.common.blocks.GT_Block_Ores_Abstract;
+import gregtech.common.blocks.GT_TileEntity_Ores;
+import ic2.api.crops.CropCard;
+import ic2.api.crops.Crops;
+import ic2.api.crops.ICropTile;
+import speiger.src.crops.api.ICropCardInfo;
+
+public class GT_BaseCrop extends CropCard implements ICropCardInfo {
+
+ public static ArrayList<GT_BaseCrop> sCropList = new ArrayList<>();
+ private String mName = E;
+ private String mDiscoveredBy = "Gregorius Techneticies";
+ private String[] mAttributes;
+ private int mTier = 0;
+ private int mMaxSize = 0;
+ private int mAfterHarvestSize = 0;
+ private int mHarvestSize = 0;
+ private final int[] mStats = new int[5];
+ private final int mGrowthSpeed = 0;
+ private ItemStack mDrop = null;
+ private ItemStack[] mSpecialDrops = null;
+ private Materials mBlock = null;
+ private static boolean bIc2NeiLoaded = IC2CropPlugin.isModLoaded();
+
+ /**
+ * To create new Crops
+ *
+ * @param aID Default ID
+ * @param aCropName Name of the Crop
+ * @param aDiscoveredBy The one who discovered the Crop
+ * @param aDrop The Item which is dropped by the Crop. must be != null
+ * @param aBaseSeed Baseseed to plant this Crop. null == crossbreed only
+ * @param aTier tier of the Crop. forced to be >= 1
+ * @param aMaxSize maximum Size of the Crop. forced to be >= 3
+ * @param aGrowthSpeed how fast the Crop grows. if < 0 then its set to Tier*300
+ * @param aHarvestSize the size the Crop needs to be harvested. forced to be between 2 and max size
+ */
+ public GT_BaseCrop(int aID, String aCropName, String aDiscoveredBy, ItemStack aBaseSeed, int aTier, int aMaxSize,
+ int aGrowthSpeed, int aAfterHarvestSize, int aHarvestSize, int aStatChemical, int aStatFood, int aStatDefensive,
+ int aStatColor, int aStatWeed, String[] aAttributes, ItemStack aDrop, ItemStack[] aSpecialDrops) {
+ new GT_BaseCrop(
+ aID,
+ aCropName,
+ aDiscoveredBy,
+ aBaseSeed,
+ aTier,
+ aMaxSize,
+ aGrowthSpeed,
+ aAfterHarvestSize,
+ aHarvestSize,
+ aStatChemical,
+ aStatFood,
+ aStatDefensive,
+ aStatColor,
+ aStatWeed,
+ aAttributes,
+ null,
+ aDrop,
+ aSpecialDrops);
+ }
+
+ /**
+ * To create new Crops
+ *
+ * @param aID Default ID
+ * @param aCropName Name of the Crop
+ * @param aDiscoveredBy The one who discovered the Crop
+ * @param aDrop The Item which is dropped by the Crop. must be != null
+ * @param aBaseSeed Baseseed to plant this Crop. null == crossbreed only
+ * @param aTier tier of the Crop. forced to be >= 1
+ * @param aMaxSize maximum Size of the Crop. forced to be >= 3
+ * @param aGrowthSpeed how fast the Crop grows. if < 0 then its set to Tier*300
+ * @param aHarvestSize the size the Crop needs to be harvested. forced to be between 2 and max size
+ * @param aBlock the block below needed for crop to grow. If null no block needed
+ */
+ public GT_BaseCrop(int aID, String aCropName, String aDiscoveredBy, ItemStack aBaseSeed, int aTier, int aMaxSize,
+ int aGrowthSpeed, int aAfterHarvestSize, int aHarvestSize, int aStatChemical, int aStatFood, int aStatDefensive,
+ int aStatColor, int aStatWeed, String[] aAttributes, Materials aBlock, ItemStack aDrop,
+ ItemStack[] aSpecialDrops) {
+ mName = aCropName;
+ aID = GT_Config.addIDConfig(ConfigCategories.IDs.crops, mName.replaceAll(" ", "_"), aID);
+ if (aDiscoveredBy != null && !aDiscoveredBy.equals(E)) mDiscoveredBy = aDiscoveredBy;
+ if (aDrop != null && aID > 0 && aID < 256) {
+ mDrop = GT_Utility.copyOrNull(aDrop);
+ mSpecialDrops = aSpecialDrops;
+ mTier = Math.max(1, aTier);
+ mMaxSize = Math.max(3, aMaxSize);
+ mHarvestSize = Math.min(Math.max(aHarvestSize, 2), mMaxSize);
+ mAfterHarvestSize = Math.min(Math.max(aAfterHarvestSize, 1), mMaxSize - 1);
+ mStats[0] = aStatChemical;
+ mStats[1] = aStatFood;
+ mStats[2] = aStatDefensive;
+ mStats[3] = aStatColor;
+ mStats[4] = aStatWeed;
+ mAttributes = aAttributes;
+ mBlock = aBlock;
+ if (!Crops.instance.registerCrop(this, aID))
+ throw new GT_ItsNotMyFaultException("Make sure the Crop ID is valid!");
+ if (aBaseSeed != null) Crops.instance.registerBaseSeed(aBaseSeed, this, 1, 1, 1, 1);
+ sCropList.add(this);
+ }
+ if (bIc2NeiLoaded) {
+ try {
+ Class.forName("speiger.src.crops.api.CropPluginAPI")
+ .getMethod("registerCropInfo", Class.forName("speiger.src.crops.api.ICropCardInfo"))
+ .invoke(
+ Class.forName("speiger.src.crops.api.CropPluginAPI")
+ .getField("instance"),
+ this);
+ } catch (IllegalAccessException | ClassNotFoundException | SecurityException | NoSuchMethodException
+ | NoSuchFieldException | InvocationTargetException | IllegalArgumentException ex) {
+ bIc2NeiLoaded = false;
+ }
+ }
+ }
+
+ @Override
+ public byte getSizeAfterHarvest(ICropTile crop) {
+ return (byte) mAfterHarvestSize;
+ }
+
+ @Override
+ public int growthDuration(ICropTile aCrop) {
+ if (mGrowthSpeed < 200) return super.growthDuration(aCrop);
+ return tier() * mGrowthSpeed;
+ }
+
+ @Override
+ public int getrootslength(ICropTile crop) {
+ return 5;
+ }
+
+ @Override
+ public String[] attributes() {
+ return mAttributes;
+ }
+
+ @Override
+ public String discoveredBy() {
+ return mDiscoveredBy;
+ }
+
+ @Override
+ public final boolean canGrow(ICropTile aCrop) {
+ // block check is only performed at the last stage of growth
+ if (this.needsBlockBelow() && aCrop.getSize() == mMaxSize - 1) {
+ return isBlockBelow(aCrop);
+ }
+ return aCrop.getSize() < maxSize();
+ }
+
+ @Override
+ public final boolean canBeHarvested(ICropTile aCrop) {
+ return aCrop.getSize() >= mHarvestSize;
+ }
+
+ @Override
+ public boolean canCross(ICropTile aCrop) {
+ return aCrop.getSize() + 2 > maxSize();
+ }
+
+ @Override
+ public int stat(int n) {
+ if (n < 0 || n >= mStats.length) return 0;
+ return mStats[n];
+ }
+
+ @Override
+ public String name() {
+ return mName;
+ }
+
+ @Override
+ public int tier() {
+ return mTier;
+ }
+
+ @Override
+ public int maxSize() {
+ return mMaxSize;
+ }
+
+ @Override
+ public ItemStack getGain(ICropTile aCrop) {
+ int tDrop = 0;
+ if (mSpecialDrops != null && (tDrop = java.util.concurrent.ThreadLocalRandom.current()
+ .nextInt(0, (mSpecialDrops.length * 2) + 2)) < mSpecialDrops.length && mSpecialDrops[tDrop] != null) {
+ return GT_Utility.copyOrNull(mSpecialDrops[tDrop]);
+ }
+ return GT_Utility.copyOrNull(mDrop);
+ }
+
+ @Override
+ public boolean rightclick(ICropTile aCrop, EntityPlayer aPlayer) {
+ if (!canBeHarvested(aCrop)) return false;
+ return aCrop.harvest(aPlayer instanceof EntityPlayerMP);
+ }
+
+ @Override
+ public int getOptimalHavestSize(ICropTile crop) {
+ return maxSize();
+ }
+
+ /**
+ * Checks if the crop needs a block below it
+ *
+ * @return True if the crop needs a block below it to grow to its max size
+ */
+ public boolean needsBlockBelow() {
+ return GT_Mod.gregtechproxy.mCropNeedBlock && this.mBlock != null;
+ }
+
+ public boolean isBlockBelow(ICropTile aCrop) {
+ if (aCrop == null) {
+ return false;
+ }
+ for (int i = 1; i < this.getrootslength(aCrop); i++) {
+ Block tBlock = aCrop.getWorld()
+ .getBlock(aCrop.getLocation().posX, aCrop.getLocation().posY - i, aCrop.getLocation().posZ);
+ if ((tBlock instanceof GT_Block_Ores_Abstract)) {
+ TileEntity tTileEntity = aCrop.getWorld()
+ .getTileEntity(aCrop.getLocation().posX, aCrop.getLocation().posY - i, aCrop.getLocation().posZ);
+ if ((tTileEntity instanceof GT_TileEntity_Ores)) {
+ Materials tMaterial = GregTech_API.sGeneratedMaterials[(((GT_TileEntity_Ores) tTileEntity).mMetaData
+ % 1000)];
+ if ((tMaterial != null) && (tMaterial != Materials._NULL)) {
+ return tMaterial == mBlock;
+ }
+ }
+ } else {
+ int tMetaID = aCrop.getWorld()
+ .getBlockMetadata(aCrop.getLocation().posX, aCrop.getLocation().posY - i, aCrop.getLocation().posZ);
+ if (isBlockBelow(new ItemStack(tBlock, 1, tMetaID))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * An isolated function to check if an item stack is a block that should be below this crop
+ *
+ * @param aItem a stack of the block placed under the crop
+ * @return The result of the check
+ */
+ public boolean isBlockBelow(ItemStack aItem) {
+ // safety in case someone calls this without checking if we have a block
+ if (!this.needsBlockBelow()) return true;
+
+ // get material from stack
+ ItemData tAssociation = GT_OreDictUnificator.getAssociation(aItem);
+ if (tAssociation == null) return false;
+
+ // return true if it's an ore of the material
+ // note: some ores don't appear to have associations in testing, naq ore is an example of that
+ if (tAssociation.mPrefix.toString()
+ .startsWith("ore") && tAssociation.mMaterial.mMaterial == mBlock) {
+ return true;
+ }
+
+ // return true if it's a block of the material
+ if (tAssociation.mPrefix == OrePrefixes.block && tAssociation.mMaterial.mMaterial == mBlock) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public List<String> getCropInformation() {
+ if (mBlock != null) {
+ ArrayList<String> result = new ArrayList<>(1);
+ result.add(
+ String.format(
+ "Requires %s Ore or Block of %s as soil block to reach full growth.",
+ mBlock.mName,
+ mBlock.mName));
+ return result;
+ }
+ return null;
+ }
+
+ @Override
+ public ItemStack getDisplayItem() {
+ if (mSpecialDrops != null && mSpecialDrops[mSpecialDrops.length - 1] != null) {
+ return GT_Utility.copyOrNull(mSpecialDrops[mSpecialDrops.length - 1]);
+ }
+ return GT_Utility.copyOrNull(mDrop);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_BlockMap.java b/src/main/java/gregtech/api/util/GT_BlockMap.java
new file mode 100644
index 0000000000..9ffe273cac
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_BlockMap.java
@@ -0,0 +1,134 @@
+package gregtech.api.util;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+
+import net.minecraft.block.Block;
+
+import gnu.trove.map.TByteObjectMap;
+import gnu.trove.map.hash.TByteObjectHashMap;
+
+public class GT_BlockMap<V> {
+
+ public static final byte WILDCARD = -1;
+ private final ConcurrentHashMap<Block, TByteObjectMap<V>> backing = new ConcurrentHashMap<>();
+ private int size = 0;
+
+ private TByteObjectMap<V> getSubmap(Block block) {
+ return backing.computeIfAbsent(block, b -> new TByteObjectHashMap<>());
+ }
+
+ /**
+ * Associate a value with that union key
+ *
+ * @param block block
+ * @param meta meta
+ * @return old mapping, or null if that doesn't exist
+ */
+ public V put(Block block, byte meta, V value) {
+ V v = getSubmap(block).put(meta, value);
+ if (v == null) size++;
+ return v;
+ }
+
+ /**
+ * Associate a value with that union key ONLY IF there isn't a prior EXACT mapping
+ *
+ * @param block block
+ * @param meta meta
+ * @return old mapping, or null if that doesn't exist
+ */
+ public V putIfAbsent(Block block, byte meta, V value) {
+ V v = getSubmap(block).putIfAbsent(meta, value);
+ if (v == null) size++;
+ return v;
+ }
+
+ /**
+ * Associate a value with that union key ONLY IF there isn't a prior EXACT mapping
+ *
+ * @param block block
+ * @param meta meta
+ * @return old mapping, or null if that doesn't exist
+ */
+ public V computeIfAbsent(Block block, byte meta, BiFunction<Block, Byte, V> function) {
+ TByteObjectMap<V> submap = getSubmap(block);
+ V v = submap.get(meta);
+ if (v == null) {
+ v = function.apply(block, meta);
+ submap.put(meta, v);
+ size++;
+ }
+ return v;
+ }
+
+ /**
+ * Contains an associated value
+ *
+ * @param block block
+ * @param meta meta
+ * @return current mapping OR wildcard of that mapping exists
+ */
+ public boolean containsKey(Block block, byte meta) {
+ TByteObjectMap<V> submap = backing.get(block);
+ if (submap == null) return false;
+ return submap.containsKey(meta) || submap.containsKey(WILDCARD);
+ }
+
+ /**
+ * Get the associated value
+ *
+ * @param block block
+ * @param meta meta
+ * @return current mapping OR wildcard of that block. null if neither exists
+ */
+ public V get(Block block, byte meta) {
+ TByteObjectMap<V> submap = backing.get(block);
+ if (submap == null) return null;
+ V v = submap.get(meta);
+ if (v != null) return v;
+ return submap.get(WILDCARD);
+ }
+
+ /**
+ * Remove a mapping
+ *
+ * @param block block
+ * @param meta meta
+ * @return old value, or null if none
+ */
+ public V remove(Block block, byte meta) {
+ TByteObjectMap<V> submap = backing.get(block);
+ if (submap == null) return null;
+ V v = submap.remove(meta);
+ if (v != null) {
+ size--;
+ if (submap.isEmpty()) backing.remove(block);
+ }
+ return v;
+ }
+
+ /**
+ * Size of all mappings
+ *
+ * @return size
+ */
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GT_BlockMap<?> that = (GT_BlockMap<?>) o;
+
+ return backing.equals(that.backing);
+ }
+
+ @Override
+ public int hashCode() {
+ return backing.hashCode();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_BlockSet.java b/src/main/java/gregtech/api/util/GT_BlockSet.java
new file mode 100644
index 0000000000..1d13afd056
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_BlockSet.java
@@ -0,0 +1,39 @@
+package gregtech.api.util;
+
+import net.minecraft.block.Block;
+
+public class GT_BlockSet {
+
+ private final GT_BlockMap<Object> backing = new GT_BlockMap<>();
+
+ public boolean add(Block block, byte meta) {
+ return backing.put(block, meta, this) != this;
+ }
+
+ public boolean contains(Block block, byte meta) {
+ return backing.get(block, meta) == this;
+ }
+
+ public boolean remove(Block block, byte meta) {
+ return backing.remove(block, meta) == this;
+ }
+
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GT_BlockSet that = (GT_BlockSet) o;
+
+ return backing.equals(that.backing);
+ }
+
+ @Override
+ public int hashCode() {
+ return backing.hashCode();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_CLS_Compat.java b/src/main/java/gregtech/api/util/GT_CLS_Compat.java
new file mode 100644
index 0000000000..c560435e30
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CLS_Compat.java
@@ -0,0 +1,157 @@
+package gregtech.api.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import cpw.mods.fml.common.ProgressManager;
+import gregtech.GT_Mod;
+import gregtech.api.enums.Materials;
+import gregtech.common.GT_Proxy;
+import gregtech.loaders.postload.GT_PostLoad;
+
+@SuppressWarnings("rawtypes, unchecked, deprecation")
+public class GT_CLS_Compat {
+
+ private static Class alexiilMinecraftDisplayer;
+ private static Class alexiilProgressDisplayer;
+ private static Class cpwProgressBar;
+
+ private static Method getLastPercent;
+ private static Method displayProgress;
+
+ private static Field isReplacingVanillaMaterials;
+ private static Field isRegisteringGTmaterials;
+ private static Field progressBarStep;
+
+ static {
+ // CLS
+ try {
+ alexiilMinecraftDisplayer = Class.forName("alexiil.mods.load.MinecraftDisplayer");
+ alexiilProgressDisplayer = Class.forName("alexiil.mods.load.ProgressDisplayer");
+ } catch (ClassNotFoundException ex) {
+ GT_Mod.GT_FML_LOGGER.catching(ex);
+ }
+
+ try {
+ cpwProgressBar = Class.forName("cpw.mods.fml.common.ProgressManager$ProgressBar");
+ } catch (ClassNotFoundException ex) {
+ GT_Mod.GT_FML_LOGGER.catching(ex);
+ }
+
+ Optional.ofNullable(alexiilMinecraftDisplayer)
+ .ifPresent(e -> {
+ try {
+ getLastPercent = e.getMethod("getLastPercent");
+ isReplacingVanillaMaterials = e.getField("isReplacingVanillaMaterials");
+ isRegisteringGTmaterials = e.getField("isRegisteringGTmaterials");
+ } catch (NoSuchMethodException | NoSuchFieldException ex) {
+ GT_Mod.GT_FML_LOGGER.catching(ex);
+ }
+ });
+
+ Optional.ofNullable(alexiilProgressDisplayer)
+ .ifPresent(e -> {
+ try {
+ displayProgress = e.getMethod("displayProgress", String.class, float.class);
+ } catch (NoSuchMethodException ex) {
+ GT_Mod.GT_FML_LOGGER.catching(ex);
+ }
+ });
+
+ try {
+ progressBarStep = cpwProgressBar.getDeclaredField("step");
+ progressBarStep.setAccessible(true);
+ } catch (NoSuchFieldException ex) {
+ GT_Mod.GT_FML_LOGGER.catching(ex);
+ }
+ }
+
+ private GT_CLS_Compat() {}
+
+ private static <T> void registerAndReportProgression(String materialsType, Collection<T> materials,
+ ProgressManager.ProgressBar progressBar, Function<T, Object> getName, Consumer<T> action) {
+ int sizeStep = materials.size();
+ final long progressionReportsEvery = 100;
+ final long bakingMsgEvery = 1000;
+ long nextProgressionReportAt = 0;
+ long nextBakingMsgAt = 0;
+ int currentStep = 0;
+
+ for (T m : materials) {
+ long now = System.currentTimeMillis();
+
+ if (nextProgressionReportAt < now) {
+ nextProgressionReportAt = now + progressionReportsEvery;
+ String materialName = getName.apply(m)
+ .toString();
+ try {
+ displayProgress.invoke(null, materialName, (float) currentStep / sizeStep);
+ } catch (IllegalAccessException | InvocationTargetException iae) {
+ GT_Mod.GT_FML_LOGGER.error("While updating progression", iae);
+ }
+ try {
+ progressBarStep.set(progressBar, currentStep);
+ } catch (IllegalAccessException iae) {
+ GT_Mod.GT_FML_LOGGER.error("While updating intermediate progression steps number", iae);
+ }
+ progressBar.step(materialName);
+ }
+ if (nextBakingMsgAt < now) {
+ nextBakingMsgAt = now + bakingMsgEvery;
+ GT_Mod.GT_FML_LOGGER
+ .info(String.format("%s - Baking: %d%%", materialsType, currentStep * 100 / sizeStep));
+ }
+ action.accept(m);
+ currentStep += 1;
+ }
+ GT_Mod.GT_FML_LOGGER.info(String.format("%s - Baking: Done", materialsType));
+ try {
+ progressBarStep.set(progressBar, currentStep);
+ } catch (IllegalAccessException iae) {
+ GT_Mod.GT_FML_LOGGER.error("While updating final progression steps number", iae);
+ }
+ }
+
+ public static void stepMaterialsCLS(Collection<GT_Proxy.OreDictEventContainer> mEvents,
+ ProgressManager.ProgressBar progressBar) throws IllegalAccessException {
+ try {
+ isRegisteringGTmaterials.set(null, true);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ GT_Mod.GT_FML_LOGGER.catching(e);
+ }
+ registerAndReportProgression(
+ "GregTech materials",
+ mEvents,
+ progressBar,
+ m -> m.mMaterial,
+ GT_Proxy::registerRecipes);
+ ProgressManager.pop(progressBar);
+ isRegisteringGTmaterials.set(null, false);
+ }
+
+ public static void doActualRegistrationCLS(ProgressManager.ProgressBar progressBar,
+ Set<Materials> replacedVanillaItemsSet) {
+ try {
+ isReplacingVanillaMaterials.set(null, true);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ GT_Mod.GT_FML_LOGGER.catching(e);
+ }
+ registerAndReportProgression(
+ "Vanilla materials",
+ replacedVanillaItemsSet,
+ progressBar,
+ m -> m.mDefaultLocalName,
+ GT_PostLoad::doActualRegistration);
+ }
+
+ public static void pushToDisplayProgress() throws InvocationTargetException, IllegalAccessException {
+ isReplacingVanillaMaterials.set(null, false);
+ displayProgress.invoke(null, "Post Initialization: loading GregTech", getLastPercent.invoke(null));
+ }
+}
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..bb73cf77ed
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
@@ -0,0 +1,494 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.Mods.GregTech;
+
+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.io.UncheckedIOException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+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;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+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 cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+
+/**
+ * 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(Math.floorDiv(aChunkX, regionLength), Math.floorDiv(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(SuperRegion::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 (Stream<Path> stream = Files.list(getSaveDirectory(w).toPath())) {
+ Map<ChunkCoordIntPair, SuperRegion> worldData = stream.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 | UncheckedIOException e) {
+ GT_Log.err.println("Error loading all region");
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ protected File getSaveDirectory(World w) {
+ File base;
+ if (w.provider.getSaveFolder() == null) base = w.getSaveHandler()
+ .getWorldDirectory();
+ else base = new File(
+ w.getSaveHandler()
+ .getWorldDirectory(),
+ w.provider.getSaveFolder());
+ return new File(base, GregTech.ID);
+ }
+
+ 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;
+ /**
+ * Be aware, this means region coord, not bottom-left chunk coord
+ */
+ private final ChunkCoordIntPair coord;
+
+ private SuperRegion(World world, int regionX, int regionZ) {
+ this.world = new WeakReference<>(world);
+ this.coord = new ChunkCoordIntPair(regionX, regionZ);
+ backingStorage = new File(getSaveDirectory(world), String.format("%s.%d.%d.dat", mId, regionX, regionZ));
+ if (backingStorage.isFile()) load();
+ }
+
+ private SuperRegion(World world, ChunkCoordIntPair regionCoord) {
+ this.world = new WeakReference<>(world);
+ this.coord = regionCoord;
+ backingStorage = new File(
+ getSaveDirectory(world),
+ String.format("%s.%d.%d.dat", mId, regionCoord.chunkXPos, regionCoord.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 * regionLength;
+ }
+
+ private int getChunkZ(int index) {
+ return index % regionLength + coord.chunkZPos * regionLength;
+ }
+
+ 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 {
+ // 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))) {
+ byte b = input.readByte();
+ if (b == 0) {
+ loadV0(input, world);
+ } else {
+ GT_Log.err.printf("Unknown ChunkAssociatedData version %d\n", b);
+ }
+ }
+ }
+
+ private void loadV0(DataInput input, World world) throws IOException {
+ int version = input.readByte();
+ 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;
+ }
+ nullRange = !nullRange;
+ }
+ }
+
+ 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_CircuitryBehavior.java b/src/main/java/gregtech/api/util/GT_CircuitryBehavior.java
new file mode 100644
index 0000000000..63fb7d1e70
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CircuitryBehavior.java
@@ -0,0 +1,212 @@
+package gregtech.api.util;
+
+import net.minecraftforge.common.util.ForgeDirection;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.GregTech_API;
+import gregtech.api.interfaces.IRedstoneCircuitBlock;
+
+/**
+ * Redstone Circuit Control Code
+ * <p/>
+ * This should make everything possible what a Redstone Computer or BuildCraft Gate could do. It is intended to use this
+ * similar to BC-Gates (for acquiring Data) and RP Logic Gates. You could write an extremely specified and complex Logic
+ * Gate, which works only for you Setup, like with ComputerCraft, but you would have to write an extra Mod to add that,
+ * as it doesn't work Ingame.
+ * <p/>
+ * One can make use of the fact, that ItemStacks can be stored as Integer, so that you can scan Inventories for specific
+ * Items using that. Luckily the Buttons in the GUI enable Copy/Paste of ItemID+MetaData to Integer, including the
+ * WildCard Damage Value when you use rightclick to place it. You just need to use @GT_Utility.stackToInt(ItemStack
+ * aStack) to get it.
+ * <p/>
+ * All Functions run usually in a seperate try/catch Block, so that failed Logic won't crash the TileEntity.
+ */
+public abstract class GT_CircuitryBehavior {
+
+ /**
+ * @param aIndex 0 - 1023 are my own Indices, so use other Numbers!
+ */
+ public GT_CircuitryBehavior(int aIndex) {
+ GregTech_API.sCircuitryBehaviors.put(aIndex, this);
+ }
+
+ /**
+ * returns if there is Redstone applied to any of the valid Inputs (OR)
+ */
+ public static boolean getAnyRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ if (aRedstoneCircuitBlock.getInputRedstone(side) > 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * returns if there is Redstone applied to all the valid Inputs (AND)
+ */
+ public static boolean getAllRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ if (aRedstoneCircuitBlock.getInputRedstone(side) == 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * returns if there is Redstone applied to exactly one of the valid Inputs (XOR)
+ */
+ public static boolean getOneRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ int tRedstoneAmount = 0;
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ if (aRedstoneCircuitBlock.getInputRedstone(side) > 0) {
+ tRedstoneAmount++;
+ }
+ }
+ }
+ return tRedstoneAmount == 1;
+ }
+
+ /**
+ * returns the strongest incoming RS-Power
+ */
+ public static byte getStrongestRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ byte tRedstoneAmount = 0;
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ tRedstoneAmount = (byte) Math.max(tRedstoneAmount, aRedstoneCircuitBlock.getInputRedstone(side));
+ }
+ }
+ return tRedstoneAmount;
+ }
+
+ // region GUI Functions
+
+ /**
+ * returns the weakest incoming non-zero RS-Power
+ */
+ public static byte getWeakestNonZeroRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ if (!getAnyRedstone(aRedstoneCircuitBlock)) return 0;
+ byte tRedstoneAmount = 15;
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ if (aRedstoneCircuitBlock.getInputRedstone(side) > 0)
+ tRedstoneAmount = (byte) Math.min(tRedstoneAmount, aRedstoneCircuitBlock.getInputRedstone(side));
+ }
+ }
+ return tRedstoneAmount;
+ }
+
+ /**
+ * returns the weakest incoming RS-Power
+ */
+ public static byte getWeakestRedstone(IRedstoneCircuitBlock aRedstoneCircuitBlock) {
+ if (!getAnyRedstone(aRedstoneCircuitBlock)) return 0;
+ byte tRedstoneAmount = 15;
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (side != aRedstoneCircuitBlock.getOutputFacing() && aRedstoneCircuitBlock.getCover(side)
+ .letsRedstoneGoIn(
+ side,
+ aRedstoneCircuitBlock.getCoverID(side),
+ aRedstoneCircuitBlock.getCoverVariable(side),
+ aRedstoneCircuitBlock.getOwnTileEntity())) {
+ tRedstoneAmount = (byte) Math.min(tRedstoneAmount, aRedstoneCircuitBlock.getInputRedstone(side));
+ }
+ }
+ return tRedstoneAmount;
+ }
+
+ /**
+ * Initializes the Parameters of this Circuit, all Parameters have been set to 0 right before calling this
+ *
+ * @param aCircuitData, The Data Storage you can use (8 Slots)
+ * @param aRedstoneCircuitBlock, The Circuit Block MetaTileEntity itself
+ */
+ public abstract void initParameters(int[] aCircuitData, IRedstoneCircuitBlock aRedstoneCircuitBlock);
+
+ /**
+ * Validates the Parameters of this Circuit when a value has been changed by the GUI Also called right
+ * after @initParameters and when the Chunk reloads
+ *
+ * @param aCircuitData, The Data Storage you can use (8 Slots and only the first 4 are User definable)
+ * @param aRedstoneCircuitBlock, The Circuit Block MetaTileEntity itself
+ */
+ public abstract void validateParameters(int[] aCircuitData, IRedstoneCircuitBlock aRedstoneCircuitBlock);
+
+ // endregion
+
+ // region Utility Functions
+
+ /**
+ * Called every tick if the Block has enough Energy and if the Block is Active
+ *
+ * @param aCircuitData, The Data Storage you can use (8 Slots)
+ * @param aRedstoneCircuitBlock, The Circuit Block MetaTileEntity itself
+ */
+ public abstract void onTick(int[] aCircuitData, IRedstoneCircuitBlock aRedstoneCircuitBlock);
+
+ /**
+ * If the ItemStack should be displayed. Parameters are between 0 and 3.
+ */
+ public abstract boolean displayItemStack(int[] aCircuitData, IRedstoneCircuitBlock aRedstoneCircuitBlock,
+ int aIndex);
+
+ /**
+ * The Name of the Gate for the GUI
+ */
+ @SideOnly(Side.CLIENT)
+ public abstract String getName();
+
+ /**
+ * The Description of the Gate for the GUI
+ */
+ @SideOnly(Side.CLIENT)
+ public abstract String getDescription();
+
+ /**
+ * The Description of the Data Field for the GUI
+ */
+ @SideOnly(Side.CLIENT)
+ public abstract String getDataDescription(int[] aCircuitData, int aCircuitDataIndex);
+
+ /**
+ * How the Integer should be displayed in the GUI. null means, that it just displays as regular Number.
+ */
+ @SideOnly(Side.CLIENT)
+ public String getDataDisplay(int[] aCircuitData, int aCircuitDataIndex) {
+ return null;
+ }
+ // endregion
+}
diff --git a/src/main/java/gregtech/api/util/GT_ClientPreference.java b/src/main/java/gregtech/api/util/GT_ClientPreference.java
new file mode 100644
index 0000000000..8df4ef8b05
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ClientPreference.java
@@ -0,0 +1,41 @@
+package gregtech.api.util;
+
+public class GT_ClientPreference {
+
+ private final boolean mSingleBlockInitialFilter;
+ private final boolean mSingleBlockInitialMultiStack;
+ private final boolean mInputBusInitialFilter;
+ private final boolean wailaAverageNS;
+
+ public GT_ClientPreference(boolean mSingleBlockInitialFilter, boolean mSingleBlockInitialMultiStack,
+ boolean mInputBusInitialFilter, boolean wailaAverageNS) {
+ this.mSingleBlockInitialFilter = mSingleBlockInitialFilter;
+ this.mSingleBlockInitialMultiStack = mSingleBlockInitialMultiStack;
+ this.mInputBusInitialFilter = mInputBusInitialFilter;
+ this.wailaAverageNS = wailaAverageNS;
+ }
+
+ public GT_ClientPreference(GT_Config aClientDataFile) {
+ this.mSingleBlockInitialFilter = aClientDataFile.get("preference", "mSingleBlockInitialFilter", false);
+ this.mSingleBlockInitialMultiStack = aClientDataFile
+ .get("preference", "mSingleBlockInitialAllowMultiStack", false);
+ this.mInputBusInitialFilter = aClientDataFile.get("preference", "mInputBusInitialFilter", true);
+ this.wailaAverageNS = aClientDataFile.get("waila", "WailaAverageNS", false);
+ }
+
+ public boolean isSingleBlockInitialFilterEnabled() {
+ return mSingleBlockInitialFilter;
+ }
+
+ public boolean isSingleBlockInitialMultiStackEnabled() {
+ return mSingleBlockInitialMultiStack;
+ }
+
+ public boolean isInputBusInitialFilterEnabled() {
+ return mInputBusInitialFilter;
+ }
+
+ public boolean isWailaAverageNSEnabled() {
+ return wailaAverageNS;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Config.java b/src/main/java/gregtech/api/util/GT_Config.java
new file mode 100644
index 0000000000..dc56def68f
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Config.java
@@ -0,0 +1,160 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.common.config.Property;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+
+public class GT_Config implements Runnable {
+
+ public static boolean troll = false;
+
+ public static Configuration sConfigFileIDs;
+ public final Configuration mConfig;
+
+ public GT_Config(Configuration aConfig) {
+ mConfig = aConfig;
+ mConfig.load();
+ mConfig.save();
+ GregTech_API.sAfterGTPreload.add(this); // in case of crash on startup
+ GregTech_API.sAfterGTLoad.add(this); // in case of crash on startup
+ GregTech_API.sAfterGTPostload.add(this);
+ if (GT_Values.lateConfigSave) GregTech_API.sFirstWorldTick.add(this);
+ }
+
+ private static boolean shouldSave() {
+ return GT_Values.lateConfigSave ? GT_Values.worldTickHappened : GregTech_API.sPostloadFinished;
+ }
+
+ public static int addIDConfig(Object aCategory, String aName, int aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = sConfigFileIDs.get(
+ aCategory.toString()
+ .replaceAll("\\|", "."),
+ aName.replaceAll("\\|", "."),
+ aDefault);
+ int rResult = tProperty.getInt(aDefault);
+ if (!tProperty.wasRead() && shouldSave()) sConfigFileIDs.save();
+ return rResult;
+ }
+
+ public static String getStackConfigName(ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return E;
+ Object rName = GT_OreDictUnificator.getAssociation(aStack);
+ if (rName != null) return rName.toString();
+ try {
+ if (GT_Utility.isStringValid(rName = aStack.getUnlocalizedName())) return rName.toString();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ String sName = aStack.getItem()
+ .toString();
+ String[] tmp = sName.split("@");
+ if (tmp.length > 0) sName = tmp[0];
+ return sName + "." + aStack.getItemDamage();
+ }
+
+ public boolean get(Object aCategory, ItemStack aStack, boolean aDefault) {
+ String aName = getStackConfigName(aStack);
+ return get(aCategory, aName, aDefault);
+ }
+
+ public boolean get(Object aCategory, String aName, boolean aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ (aName + "_" + aDefault).replaceAll("\\|", "_"),
+ aDefault);
+ boolean rResult = tProperty.getBoolean(aDefault);
+ if (!tProperty.wasRead() && shouldSave()) mConfig.save();
+ return rResult;
+ }
+
+ public int get(Object aCategory, ItemStack aStack, int aDefault) {
+ return get(aCategory, getStackConfigName(aStack), aDefault);
+ }
+
+ public int get(Object aCategory, String aName, int aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ (aName + "_" + aDefault).replaceAll("\\|", "_"),
+ aDefault);
+ int rResult = tProperty.getInt(aDefault);
+ if (!tProperty.wasRead() && shouldSave()) mConfig.save();
+ return rResult;
+ }
+
+ public double get(Object aCategory, ItemStack aStack, double aDefault) {
+ return get(aCategory, getStackConfigName(aStack), aDefault);
+ }
+
+ public double get(Object aCategory, String aName, double aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ (aName + "_" + aDefault).replaceAll("\\|", "_"),
+ aDefault);
+ double rResult = tProperty.getDouble(aDefault);
+ if (!tProperty.wasRead() && shouldSave()) mConfig.save();
+ return rResult;
+ }
+
+ public String get(Object aCategory, ItemStack aStack, String aDefault) {
+ return get(aCategory, getStackConfigName(aStack), aDefault);
+ }
+
+ public String get(Object aCategory, String aName, String aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ (aName + "_" + aDefault).replaceAll("\\|", "_"),
+ aDefault);
+ String rResult = tProperty.getString();
+ if (!tProperty.wasRead() && shouldSave()) mConfig.save();
+ return rResult;
+ }
+
+ public String[] get(Object aCategory, ItemStack aStack, String... aDefault) {
+ return get(aCategory, getStackConfigName(aStack), aDefault);
+ }
+
+ public String[] get(Object aCategory, String aName, String... aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ aName.replaceAll("\\|", "_"),
+ aDefault);
+ String[] rResult = tProperty.getStringList();
+ if (!tProperty.wasRead() && GregTech_API.sPostloadFinished) mConfig.save();
+ return rResult;
+ }
+
+ public String getWithValidValues(Object aCategory, String aName, String[] validValues, String aDefault) {
+ if (GT_Utility.isStringInvalid(aName)) return aDefault;
+ Property tProperty = mConfig.get(
+ aCategory.toString()
+ .replaceAll("\\|", "_"),
+ aName.replaceAll("\\|", "_"),
+ aDefault,
+ null,
+ validValues);
+ String rResult = tProperty.getString();
+ if (!tProperty.wasRead() && GregTech_API.sPostloadFinished) mConfig.save();
+ return rResult;
+ }
+
+ @Override
+ public void run() {
+ mConfig.save();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_CoverBehavior.java b/src/main/java/gregtech/api/util/GT_CoverBehavior.java
new file mode 100644
index 0000000000..34fc151b9a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CoverBehavior.java
@@ -0,0 +1,411 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+
+import java.lang.ref.WeakReference;
+
+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.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.gui.modularui.GT_UIInfos;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.net.GT_Packet_TileEntityCoverGUI;
+
+/**
+ * For Covers with a special behavior. Has fixed storage format of 4 byte. Not very convenient...
+ */
+public abstract class GT_CoverBehavior extends GT_CoverBehaviorBase<ISerializableObject.LegacyCoverData> {
+
+ public boolean mPlayerNotified = false;
+
+ public GT_CoverBehavior() {
+ this(null);
+ }
+
+ public GT_CoverBehavior(ITexture coverTexture) {
+ super(ISerializableObject.LegacyCoverData.class, coverTexture);
+ }
+
+ protected 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(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return isRedstoneSensitive(side, aCoverID, aCoverVariable.get(), aTileEntity, aTimer);
+ }
+
+ @Override
+ protected ISerializableObject.LegacyCoverData doCoverThingsImpl(ForgeDirection side, byte aInputRedstone,
+ int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ if (aCoverVariable == null) aCoverVariable = new ISerializableObject.LegacyCoverData();
+ aCoverVariable.set(doCoverThings(side, aInputRedstone, aCoverID, aCoverVariable.get(), aTileEntity, aTimer));
+ return aCoverVariable;
+ }
+
+ @Override
+ protected boolean onCoverRightClickImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX,
+ float aY, float aZ) {
+ return onCoverRightclick(side, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ);
+ }
+
+ @Override
+ protected ISerializableObject.LegacyCoverData onCoverScrewdriverClickImpl(ForgeDirection side, 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(side, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ));
+ return aCoverVariable;
+ }
+
+ @Override
+ protected boolean onCoverShiftRightClickImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) {
+ return onCoverShiftRightclick(side, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer);
+ }
+
+ @Deprecated
+ @Override
+ protected Object getClientGUIImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer,
+ World aWorld) {
+ return getClientGUI(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean onCoverRemovalImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, boolean aForced) {
+ return onCoverRemoval(side, aCoverID, convert(aCoverVariable), aTileEntity, aForced);
+ }
+
+ @Override
+ protected String getDescriptionImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getDescription(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected float getBlastProofLevelImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getBlastProofLevel(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsRedstoneGoInImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoIn(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsRedstoneGoOutImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsRedstoneGoOut(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsEnergyInImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyIn(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsEnergyOutImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return letsEnergyOut(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFluidInImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidIn(side, aCoverID, convert(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsFluidOutImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidOut(side, aCoverID, convert(aCoverVariable), aFluid, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsItemsInImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsIn(side, aCoverID, convert(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ @Override
+ protected boolean letsItemsOutImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) {
+ return letsItemsOut(side, aCoverID, convert(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ @Override
+ protected boolean isGUIClickableImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return isGUIClickable(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean manipulatesSidedRedstoneOutputImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return manipulatesSidedRedstoneOutput(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected boolean alwaysLookConnectedImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return alwaysLookConnected(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected byte getRedstoneInputImpl(ForgeDirection side, byte aInputRedstone, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getRedstoneInput(side, aInputRedstone, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected int getTickRateImpl(ForgeDirection side, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable,
+ ICoverable aTileEntity) {
+ return getTickRate(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected byte getLensColorImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getLensColor(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ @Override
+ protected ItemStack getDropImpl(ForgeDirection side, int aCoverID,
+ ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) {
+ return getDrop(side, aCoverID, convert(aCoverVariable), aTileEntity);
+ }
+
+ // endregion
+
+ public boolean isRedstoneSensitive(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity,
+ long aTimer) {
+ return true;
+ }
+
+ /**
+ * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time.
+ */
+ public int doCoverThings(ForgeDirection side, byte aInputRedstone, int aCoverID, int aCoverVariable,
+ ICoverable aTileEntity, long aTimer) {
+ return aCoverVariable;
+ }
+
+ /**
+ * Called when someone rightclicks this Cover.
+ * <p/>
+ * return true, if something actually happens.
+ */
+ public boolean onCoverRightclick(ForgeDirection side, int aCoverID, int 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
+ */
+ public int onCoverScrewdriverclick(ForgeDirection side, int aCoverID, int 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.
+ */
+ public boolean onCoverShiftRightclick(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity,
+ EntityPlayer aPlayer) {
+ if (hasCoverGUI() && aPlayer instanceof EntityPlayerMP) {
+ lastPlayer = new WeakReference<>(aPlayer);
+ mPlayerNotified = false;
+ if (useModularUI()) {
+ GT_UIInfos.openCoverUI(aTileEntity, aPlayer, side);
+ } else {
+ GT_Values.NW.sendToPlayer(
+ new GT_Packet_TileEntityCoverGUI(
+ side,
+ aCoverID,
+ aCoverVariable,
+ aTileEntity,
+ (EntityPlayerMP) aPlayer),
+ (EntityPlayerMP) aPlayer);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Deprecated
+ public Object getClientGUI(ForgeDirection side, int aCoverID, int coverData, ICoverable aTileEntity) {
+ 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.
+ */
+ public boolean onCoverRemoval(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity,
+ boolean aForced) {
+ return true;
+ }
+
+ /**
+ * Gives a small Text for the status of the Cover.
+ */
+ public String getDescription(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return E;
+ }
+
+ /**
+ * How Blast Proof the Cover is. 30 is normal.
+ */
+ public float getBlastProofLevel(ForgeDirection side, int aCoverID, int 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
+ */
+ public boolean letsRedstoneGoIn(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets RS-Signals out of the Block
+ */
+ public boolean letsRedstoneGoOut(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy into the Block
+ */
+ public boolean letsEnergyIn(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy out of the Block
+ */
+ public boolean letsEnergyOut(ForgeDirection side, int aCoverID, int 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.
+ */
+ public boolean letsFluidIn(ForgeDirection side, int aCoverID, int 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.
+ */
+ public boolean letsFluidOut(ForgeDirection side, int aCoverID, int 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).
+ */
+ public boolean letsItemsIn(ForgeDirection side, int aCoverID, int 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).
+ */
+ public boolean letsItemsOut(ForgeDirection side, int aCoverID, int aCoverVariable, int aSlot,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets you rightclick the Machine normally
+ */
+ public boolean isGUIClickable(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return true;
+ }
+
+ /**
+ * Needs to return true for Covers, which have a Redstone Output on their Facing.
+ */
+ public boolean manipulatesSidedRedstoneOutput(ForgeDirection side, int aCoverID, int aCoverVariable,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * if this Cover should let Pipe Connections look connected even if it is not the case.
+ */
+ public boolean alwaysLookConnected(ForgeDirection side, int aCoverID, int 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.
+ */
+ public byte getRedstoneInput(ForgeDirection side, byte aInputRedstone, int aCoverID, int aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsRedstoneGoIn(side, 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
+ */
+ public int getTickRate(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return 0;
+ }
+
+ /**
+ * The MC Color of this Lens. -1 for no Color (meaning this isn't a Lens then).
+ */
+ public byte getLensColor(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return -1;
+ }
+
+ /**
+ * @return the ItemStack dropped by this Cover
+ */
+ public ItemStack getDrop(ForgeDirection side, int aCoverID, int aCoverVariable, ICoverable aTileEntity) {
+ return GT_OreDictUnificator.get(true, aTileEntity.getCoverItemAtSide(side));
+ }
+}
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..be9492ebba
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java
@@ -0,0 +1,856 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.function.Supplier;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+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.nbt.NBTTagInt;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.gui.GT_GUIColorOverride;
+import gregtech.api.gui.modularui.GT_CoverUIBuildContext;
+import gregtech.api.gui.modularui.GT_UIInfos;
+import gregtech.api.gui.widgets.GT_CoverTickRateButton;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.net.GT_Packet_TileEntityCoverGUI;
+import gregtech.common.covers.CoverInfo;
+
+/**
+ * For Covers with a special behavior.
+ *
+ * @author glease
+ */
+public abstract class GT_CoverBehaviorBase<T extends ISerializableObject> {
+
+ public WeakReference<EntityPlayer> lastPlayer = null;
+ private final Class<T> typeToken;
+ private final ITexture coverFGTexture;
+
+ protected GT_CoverBehaviorBase(Class<T> typeToken) {
+ this(typeToken, null);
+ }
+
+ protected GT_CoverBehaviorBase(Class<T> typeToken, ITexture coverTexture) {
+ this.typeToken = typeToken;
+ this.coverFGTexture = coverTexture;
+ reloadColorOverride();
+ }
+
+ public void reloadColorOverride() {
+ this.colorOverride = GT_GUIColorOverride.get(guiTexturePath);
+ }
+
+ public abstract T createDataObject(int aLegacyData);
+
+ public abstract T createDataObject();
+
+ public final T createDataObject(NBTBase aNBT) {
+ // Handle legacy data (migrating from GT_CoverBehavior to GT_CoverBehaviorBase)
+ if (aNBT instanceof NBTTagInt) {
+ return createDataObject(((NBTTagInt) aNBT).func_150287_d());
+ }
+
+ final 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) {
+ try {
+ return typeToken.cast(aData);
+ } catch (Exception e) {
+ throw new RuntimeException("Casting data in " + this.getClass() + ", data " + aData, e);
+ }
+ }
+
+ // region facade
+
+ /**
+ * Get target facade block. Does not affect rendering of **this** block. It is only used as a hint for other block
+ * in case of CTM
+ *
+ * @return null if none, otherwise return facade target block
+ */
+ public final Block getFacadeBlock(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getFacadeBlockImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Get target facade block. Does not affect rendering of **this** block. It is only used as a hint for other block
+ * in case of CTM
+ *
+ * @return 0 if none, otherwise return facade target meta
+ */
+ public final int getFacadeMeta(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getFacadeMetaImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Get the display stack. Default to {@code int2Stack(aCoverID)}
+ */
+ public final ItemStack getDisplayStack(int aCoverID, ISerializableObject aCoverVariable) {
+ return getDisplayStackImpl(aCoverID, forceCast(aCoverVariable));
+ }
+
+ /**
+ * Get the special foreground cover texture associated with this cover. Return null if one should use the texture
+ * passed to {@link gregtech.api.GregTech_API#registerCover(ItemStack, ITexture, GT_CoverBehaviorBase)} or its
+ * overloads.
+ */
+ public final ITexture getSpecialCoverFGTexture(ForgeDirection side, int aCoverID,
+ ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getSpecialCoverFGTextureImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Get the special cover texture associated with this cover. Return null if one should use the texture passed to
+ * {@link gregtech.api.GregTech_API#registerCover(ItemStack, ITexture, GT_CoverBehaviorBase)} or its overloads.
+ */
+ public final ITexture getSpecialCoverTexture(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getSpecialCoverTextureImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Return whether cover data needs to be synced to client upon tile entity creation or cover placement.
+ * <p>
+ * Note if you want to sync the data afterwards you will have to manually do it by calling
+ * {@link ICoverable#issueCoverUpdate(ForgeDirection)} This option only affects the initial sync.
+ */
+ public final boolean isDataNeededOnClient(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return isDataNeededOnClientImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Called upon receiving data from network. Use {@link ICoverable#isClientSide()} to determine the side.
+ */
+ public final void onDataChanged(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ onDataChangedImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Called before receiving data from network. Use {@link ICoverable#isClientSide()} to determine the side.
+ */
+ public final void preDataChanged(ForgeDirection side, int aCoverID, int aNewCoverId,
+ ISerializableObject aCoverVariable, ISerializableObject aNewCoverVariable, ICoverable aTileEntity) {
+ preDataChangedImpl(
+ side,
+ aCoverID,
+ aNewCoverId,
+ forceCast(aCoverVariable),
+ forceCast(aNewCoverVariable),
+ aTileEntity);
+ }
+
+ /**
+ * Called upon cover being removed. Called on both server and client.
+ */
+ public final void onDropped(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ onDroppedImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ public final boolean isRedstoneSensitive(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, long aTimer) {
+ return isRedstoneSensitiveImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer);
+ }
+
+ /**
+ * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time.
+ */
+ public final T doCoverThings(ForgeDirection side, byte aInputRedstone, int aCoverID,
+ ISerializableObject aCoverVariable, ICoverable aTileEntity, long aTimer) {
+ return doCoverThingsImpl(side, aInputRedstone, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer);
+ }
+
+ /**
+ * Called when someone rightclicks this Cover.
+ * <p/>
+ * return true, if something actually happens.
+ */
+ public final boolean onCoverRightClick(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return onCoverRightClickImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ return onCoverScrewdriverClickImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, EntityPlayer aPlayer) {
+ return onCoverShiftRightClickImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer);
+ }
+
+ @Deprecated
+ public final Object getClientGUI(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) {
+ return getClientGUIImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity, boolean aForced) {
+ return onCoverRemovalImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity, aForced);
+ }
+
+ /**
+ * Called upon Base TE being destroyed (once getDrops is called), thus getting called only when destroyed in
+ * survival.
+ */
+ public final void onBaseTEDestroyed(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ onBaseTEDestroyedImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Gives a small Text for the status of the Cover.
+ */
+ public final String getDescription(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getDescriptionImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * How Blast Proof the Cover is. 30 is normal.
+ */
+ public final float getBlastProofLevel(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getBlastProofLevelImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsRedstoneGoInImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets RS-Signals out of the Block
+ */
+ public final boolean letsRedstoneGoOut(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsRedstoneGoOutImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Energy into the Block
+ */
+ public final boolean letsEnergyIn(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsEnergyInImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * If it lets Energy out of the Block
+ */
+ public final boolean letsEnergyOut(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsEnergyOutImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidInImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ Fluid aFluid, ICoverable aTileEntity) {
+ return letsFluidOutImpl(side, 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
+ * reaction 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable, int aSlot,
+ ICoverable aTileEntity) {
+ return letsItemsInImpl(side, 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
+ * reaction 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable, int aSlot,
+ ICoverable aTileEntity) {
+ return letsItemsOutImpl(side, aCoverID, forceCast(aCoverVariable), aSlot, aTileEntity);
+ }
+
+ /**
+ * If it lets you rightclick the Machine normally
+ */
+ public final boolean isGUIClickable(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return isGUIClickableImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Needs to return true for Covers, which have a Redstone Output on their Facing.
+ */
+ public final boolean manipulatesSidedRedstoneOutput(ForgeDirection side, int aCoverID,
+ ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return manipulatesSidedRedstoneOutputImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * if this Cover should let Pipe Connections look connected even if it is not the case.
+ */
+ public final boolean alwaysLookConnected(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return alwaysLookConnectedImpl(side, 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(ForgeDirection side, byte aInputRedstone, int aCoverID,
+ ISerializableObject aCoverVariable, ICoverable aTileEntity) {
+ return getRedstoneInputImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getTickRateImpl(side, 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(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getLensColorImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * @return the ItemStack dropped by this Cover
+ */
+ public final ItemStack getDrop(ForgeDirection side, int aCoverID, ISerializableObject aCoverVariable,
+ ICoverable aTileEntity) {
+ return getDropImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity);
+ }
+
+ /**
+ * Called when the cover is initially attached to a machine.
+ *
+ * @param player The attaching player
+ * @param aCover An {@link ItemStack} containing the cover
+ * @param aTileEntity The machine receiving the cover
+ * @param side Which side the cover is attached to
+ */
+ public void onPlayerAttach(EntityPlayer player, ItemStack aCover, ICoverable aTileEntity, ForgeDirection side) {
+ // Do nothing by default.
+ }
+
+ // endregion
+
+ // region UI stuff
+
+ protected GT_TooltipDataCache mTooltipCache = new GT_TooltipDataCache();
+ protected GT_GUIColorOverride colorOverride;
+ private static final String guiTexturePath = "gregtech:textures/gui/GuiCover.png";
+
+ /**
+ * For back compatibility, you need to override this if this cover uses ModularUI.
+ */
+ public boolean useModularUI() {
+ return false;
+ }
+
+ public ModularWindow createWindow(GT_CoverUIBuildContext buildContext) {
+ return new UIFactory(buildContext).createWindow();
+ }
+
+ /**
+ * Creates {@link ModularWindow} for this cover. This is separated from base class, as attaching the same covers in
+ * different sides of the same tile needs different UI with different context.
+ */
+ protected class UIFactory {
+
+ private final GT_CoverUIBuildContext uiBuildContext;
+
+ public UIFactory(GT_CoverUIBuildContext buildContext) {
+ this.uiBuildContext = buildContext;
+ }
+
+ public ModularWindow createWindow() {
+ ModularWindow.Builder builder = ModularWindow.builder(getGUIWidth(), getGUIHeight());
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ builder.setGuiTint(getUIBuildContext().getGuiColorization());
+ if (doesBindPlayerInventory() && !getUIBuildContext().isAnotherWindow()) {
+ builder.bindPlayerInventory(getUIBuildContext().getPlayer());
+ }
+ addTitleToUI(builder);
+ addUIWidgets(builder);
+ if (getUIBuildContext().isAnotherWindow()) {
+ builder.widget(
+ ButtonWidget.closeWindowButton(true)
+ .setPos(getGUIWidth() - 15, 3));
+ }
+
+ final CoverInfo coverInfo = uiBuildContext.getTile()
+ .getCoverInfoAtSide(uiBuildContext.getCoverSide());
+ final GT_CoverBehaviorBase<?> behavior = coverInfo.getCoverBehavior();
+ if (coverInfo.getMinimumTickRate() > 0 && behavior.allowsTickRateAddition()) {
+ builder.widget(
+ new GT_CoverTickRateButton(coverInfo, builder).setPos(getGUIWidth() - 24, getGUIHeight() - 24));
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Override this to add widgets for your UI.
+ */
+ protected void addUIWidgets(ModularWindow.Builder builder) {}
+
+ public GT_CoverUIBuildContext getUIBuildContext() {
+ return uiBuildContext;
+ }
+
+ /**
+ * Can return null when cover data is invalid e.g. tile is broken or cover is removed
+ */
+ @Nullable
+ public T getCoverData() {
+ if (isCoverValid()) {
+ return forceCast(
+ getUIBuildContext().getTile()
+ .getComplexCoverDataAtSide(getUIBuildContext().getCoverSide()));
+ } else {
+ return null;
+ }
+ }
+
+ public boolean setCoverData(T data) {
+ if (isCoverValid()) {
+ getUIBuildContext().getTile()
+ .receiveCoverData(
+ getUIBuildContext().getCoverSide(),
+ getUIBuildContext().getCoverID(),
+ data,
+ getUIBuildContext().getPlayer() instanceof EntityPlayerMP
+ ? (EntityPlayerMP) getUIBuildContext().getPlayer()
+ : null);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isCoverValid() {
+ return !getUIBuildContext().getTile()
+ .isDead()
+ && getUIBuildContext().getTile()
+ .getCoverBehaviorAtSideNew(getUIBuildContext().getCoverSide()) != GregTech_API.sNoBehavior;
+ }
+
+ protected void addTitleToUI(ModularWindow.Builder builder) {
+ ItemStack coverItem = GT_Utility.intToStack(getUIBuildContext().getCoverID());
+ if (coverItem != null) {
+ builder.widget(
+ new ItemDrawable(coverItem).asWidget()
+ .setPos(5, 5)
+ .setSize(16, 16))
+ .widget(
+ new TextWidget(coverItem.getDisplayName()).setDefaultColor(COLOR_TITLE.get())
+ .setPos(25, 9));
+ }
+ }
+
+ protected int getGUIWidth() {
+ return 176;
+ }
+
+ protected int getGUIHeight() {
+ return 107;
+ }
+
+ protected boolean doesBindPlayerInventory() {
+ return false;
+ }
+
+ protected int getTextColorOrDefault(String textType, int defaultColor) {
+ return colorOverride.getTextColorOrDefault(textType, defaultColor);
+ }
+
+ protected final Supplier<Integer> COLOR_TITLE = () -> getTextColorOrDefault("title", 0x222222);
+ protected final Supplier<Integer> COLOR_TEXT_GRAY = () -> getTextColorOrDefault("text_gray", 0x555555);
+ protected final Supplier<Integer> COLOR_TEXT_WARN = () -> getTextColorOrDefault("text_warn", 0xff0000);
+ }
+
+ // endregion
+
+ // region impl
+
+ protected Block getFacadeBlockImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return null;
+ }
+
+ protected int getFacadeMetaImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return 0;
+ }
+
+ protected ItemStack getDisplayStackImpl(int aCoverID, T aCoverVariable) {
+ return GT_Utility.intToStack(aCoverID);
+ }
+
+ protected ITexture getSpecialCoverFGTextureImpl(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return coverFGTexture;
+ }
+
+ protected ITexture getSpecialCoverTextureImpl(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return null;
+ }
+
+ protected boolean isDataNeededOnClientImpl(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ protected void onDataChangedImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {}
+
+ protected void preDataChangedImpl(ForgeDirection side, int aCoverID, int aNewCoverId, T aCoverVariable,
+ T aNewCoverVariable, ICoverable aTileEntity) {}
+
+ protected void onDroppedImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {}
+
+ protected void onBaseTEDestroyedImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {}
+
+ protected boolean isRedstoneSensitiveImpl(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity, long aTimer) {
+ return false;
+ }
+
+ /**
+ * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time.
+ */
+ protected T doCoverThingsImpl(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity, EntityPlayer aPlayer) {
+ if (hasCoverGUI() && aPlayer instanceof EntityPlayerMP) {
+ lastPlayer = new WeakReference<>(aPlayer);
+ if (useModularUI()) {
+ GT_UIInfos.openCoverUI(aTileEntity, aPlayer, side);
+ } else {
+ GT_Values.NW.sendToPlayer(
+ new GT_Packet_TileEntityCoverGUI(
+ side,
+ aCoverID,
+ aCoverVariable,
+ aTileEntity,
+ (EntityPlayerMP) aPlayer),
+ (EntityPlayerMP) aPlayer);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Deprecated
+ protected Object getClientGUIImpl(ForgeDirection side, 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(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity,
+ boolean aForced) {
+ return true;
+ }
+
+ /**
+ * Gives a small Text for the status of the Cover.
+ */
+ protected String getDescriptionImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return E;
+ }
+
+ /**
+ * How Blast Proof the Cover is. 30 is normal.
+ */
+ protected float getBlastProofLevelImpl(ForgeDirection side, 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(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets RS-Signals out of the Block
+ */
+ protected boolean letsRedstoneGoOutImpl(ForgeDirection side, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy into the Block
+ */
+ protected boolean letsEnergyInImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets Energy out of the Block
+ */
+ protected boolean letsEnergyOutImpl(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, int aCoverID, T aCoverVariable, int aSlot,
+ ICoverable aTileEntity) {
+ return false;
+ }
+
+ /**
+ * If it lets you rightclick the Machine normally
+ */
+ protected boolean isGUIClickableImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return true;
+ }
+
+ /**
+ * Needs to return true for Covers, which have a Redstone Output on their Facing.
+ */
+ protected boolean manipulatesSidedRedstoneOutputImpl(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, byte aInputRedstone, int aCoverID, T aCoverVariable,
+ ICoverable aTileEntity) {
+ return letsRedstoneGoIn(side, 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(ForgeDirection side, 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(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return -1;
+ }
+
+ /**
+ * @return the ItemStack dropped by this Cover
+ */
+ protected ItemStack getDropImpl(ForgeDirection side, int aCoverID, T aCoverVariable, ICoverable aTileEntity) {
+ return GT_OreDictUnificator.get(true, aTileEntity.getCoverItemAtSide(side));
+ }
+
+ // endregion
+
+ // region no data
+
+ /**
+ * Checks if the Cover can be placed on this.
+ */
+ public boolean isCoverPlaceable(ForgeDirection side, 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(ForgeDirection side, 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(ForgeDirection side, ItemStack aCover, ICoverable aTileEntity) {
+ aTileEntity.setCoverIDAtSide(side, GT_Utility.stackToInt(aCover));
+ }
+
+ public boolean allowsCopyPasteTool() {
+ return true;
+ }
+
+ public boolean allowsTickRateAddition() {
+ return true;
+ }
+
+ @NotNull
+ public final List<String> getAdditionalTooltip(ISerializableObject coverData) {
+ return getAdditionalTooltipImpl(forceCast(coverData));
+ }
+
+ /**
+ * Override to add to the tooltip generated when a user hovers over the cover on the left side of a machine's UI.
+ *
+ * @param coverData The cover data associated with the cover on a particular side.
+ * @return A list of new tooltip entries. Entries are inserted at the top, just after the name and direction line.
+ */
+ protected List<String> getAdditionalTooltipImpl(T coverData) {
+ return ImmutableList.of();
+ }
+ // endregion
+}
diff --git a/src/main/java/gregtech/api/util/GT_CreativeTab.java b/src/main/java/gregtech/api/util/GT_CreativeTab.java
new file mode 100644
index 0000000000..1049f40278
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_CreativeTab.java
@@ -0,0 +1,26 @@
+package gregtech.api.util;
+
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import gregtech.api.enums.ItemList;
+
+public class GT_CreativeTab extends CreativeTabs {
+
+ public GT_CreativeTab(String aName, String aLocalName) {
+ super("GregTech." + aName);
+ GT_LanguageManager.addStringLocalization("itemGroup.GregTech." + aName, aLocalName);
+ }
+
+ @Override
+ public ItemStack getIconItemStack() {
+ return ItemList.Tool_Cheat.get(1, new ItemStack(Blocks.iron_block, 1));
+ }
+
+ @Override
+ public Item getTabIconItem() {
+ return ItemList.Circuit_Integrated.getItem();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ExoticEnergyInputHelper.java b/src/main/java/gregtech/api/util/GT_ExoticEnergyInputHelper.java
new file mode 100644
index 0000000000..d59796b251
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ExoticEnergyInputHelper.java
@@ -0,0 +1,114 @@
+package gregtech.api.util;
+
+import static gregtech.api.util.GT_Utility.filterValidMTEs;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch;
+
+public class GT_ExoticEnergyInputHelper {
+
+ /**
+ * The Valid Types of TecTech Hatch List.
+ */
+ private static final List<Class<? extends GT_MetaTileEntity_Hatch>> sExoticEnergyHatchType = new ArrayList<>();
+
+ static {
+ tryRegister("com.github.technus.tectech.thing.metaTileEntity.hatch.GT_MetaTileEntity_Hatch_EnergyMulti");
+ tryRegister("com.github.technus.tectech.thing.metaTileEntity.hatch.GT_MetaTileEntity_Hatch_EnergyTunnel");
+ }
+
+ public static void register(Class<? extends GT_MetaTileEntity_Hatch> clazz) {
+ if (!GT_MetaTileEntity_Hatch.class.isAssignableFrom(clazz)) throw new IllegalArgumentException(
+ clazz.getName() + " is not a subclass of " + GT_MetaTileEntity_Hatch.class.getName());
+ sExoticEnergyHatchType.add(clazz);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void tryRegister(String className) {
+ Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ return;
+ }
+ if (!GT_MetaTileEntity_Hatch.class.isAssignableFrom(clazz)) throw new IllegalArgumentException(
+ clazz.getName() + " is not a subclass of " + GT_MetaTileEntity_Hatch.class.getName());
+ sExoticEnergyHatchType.add((Class<? extends GT_MetaTileEntity_Hatch>) clazz);
+ }
+
+ public static boolean drainEnergy(long aEU, Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ for (GT_MetaTileEntity_Hatch tHatch : hatches) {
+ long tDrain = Math.min(
+ tHatch.getBaseMetaTileEntity()
+ .getStoredEU(),
+ aEU);
+ tHatch.getBaseMetaTileEntity()
+ .decreaseStoredEnergyUnits(tDrain, false);
+ aEU -= tDrain;
+ }
+ return aEU <= 0;
+ }
+
+ public static boolean isExoticEnergyInput(IMetaTileEntity aHatch) {
+ for (Class<?> clazz : sExoticEnergyHatchType) {
+ if (clazz.isInstance(aHatch)) return true;
+ }
+ return false;
+ }
+
+ public static long getTotalEuMulti(Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ long rEU = 0L;
+ for (GT_MetaTileEntity_Hatch tHatch : filterValidMTEs(hatches)) {
+ rEU += tHatch.getBaseMetaTileEntity()
+ .getInputVoltage() * tHatch.maxWorkingAmperesIn();
+ }
+ return rEU;
+ }
+
+ public static long getMaxInputVoltageMulti(Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ long rVoltage = 0;
+ for (GT_MetaTileEntity_Hatch tHatch : filterValidMTEs(hatches)) {
+ rVoltage += tHatch.getBaseMetaTileEntity()
+ .getInputVoltage();
+ }
+ return rVoltage;
+ }
+
+ public static long getAverageInputVoltageMulti(Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ long rVoltage = 0;
+ for (GT_MetaTileEntity_Hatch tHatch : filterValidMTEs(hatches)) {
+ rVoltage += tHatch.getBaseMetaTileEntity()
+ .getInputVoltage();
+ }
+ if (hatches.isEmpty()) {
+ return 0;
+ }
+ return rVoltage / hatches.size();
+ }
+
+ public static long getMaxInputAmpsMulti(Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ long rAmp = 0;
+ for (GT_MetaTileEntity_Hatch tHatch : filterValidMTEs(hatches)) {
+ rAmp += tHatch.getBaseMetaTileEntity()
+ .getInputAmperage();
+ }
+ return rAmp;
+ }
+
+ public static long getMaxWorkingInputAmpsMulti(Collection<? extends GT_MetaTileEntity_Hatch> hatches) {
+ long rAmp = 0;
+ for (GT_MetaTileEntity_Hatch tHatch : filterValidMTEs(hatches)) {
+ rAmp += tHatch.maxWorkingAmperesIn();
+ }
+ return rAmp;
+ }
+
+ public static List<Class<? extends GT_MetaTileEntity_Hatch>> getAllClasses() {
+ return Collections.unmodifiableList(sExoticEnergyHatchType);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_FoodStat.java b/src/main/java/gregtech/api/util/GT_FoodStat.java
new file mode 100644
index 0000000000..5eda76f9d0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_FoodStat.java
@@ -0,0 +1,122 @@
+package gregtech.api.util;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Items;
+import net.minecraft.item.EnumAction;
+import net.minecraft.item.ItemStack;
+import net.minecraft.potion.PotionEffect;
+
+import gregtech.api.damagesources.GT_DamageSources;
+import gregtech.api.enums.SoundResource;
+import gregtech.api.interfaces.IFoodStat;
+import gregtech.api.items.GT_MetaBase_Item;
+
+public class GT_FoodStat implements IFoodStat {
+
+ private final int mFoodLevel;
+ private final int[] mPotionEffects;
+ private final float mSaturation;
+ private final EnumAction mAction;
+ private final ItemStack mEmptyContainer;
+ private final boolean mAlwaysEdible, mInvisibleParticles, mIsRotten;
+ private boolean mExplosive = false, mMilk = false;
+
+ /**
+ * @param aFoodLevel Amount of Food in Half Bacon [0 - 20]
+ * @param aSaturation Amount of Saturation [0.0F - 1.0F]
+ * @param aAction The Action to be used. If this is null, it uses the Eating Action
+ * @param aEmptyContainer An empty Container (Optional)
+ * @param aAlwaysEdible If this Item is always edible, like Golden Apples or Potions
+ * @param aInvisibleParticles If the Particles of the Potion Effects are invisible
+ * @param aPotionEffects An Array of Potion Effects with %4==0 Elements as follows ID of a Potion Effect. 0 for
+ * none Duration of the Potion in Ticks Level of the Effect. [0, 1, 2] are for [I, II,
+ * III] The likelihood that this Potion Effect takes place upon being eaten [1 - 100]
+ */
+ public GT_FoodStat(int aFoodLevel, float aSaturation, EnumAction aAction, ItemStack aEmptyContainer,
+ boolean aAlwaysEdible, boolean aInvisibleParticles, boolean aIsRotten, int... aPotionEffects) {
+ mFoodLevel = aFoodLevel;
+ mSaturation = aSaturation;
+ mAction = aAction == null ? EnumAction.eat : aAction;
+ mPotionEffects = aPotionEffects;
+ mEmptyContainer = GT_Utility.copyOrNull(aEmptyContainer);
+ mInvisibleParticles = aInvisibleParticles;
+ mAlwaysEdible = aAlwaysEdible;
+ mIsRotten = aIsRotten;
+ }
+
+ public GT_FoodStat setExplosive() {
+ mExplosive = true;
+ return this;
+ }
+
+ public GT_FoodStat setMilk() {
+ mMilk = true;
+ return this;
+ }
+
+ @Override
+ public int getFoodLevel(GT_MetaBase_Item aItem, ItemStack aStack, EntityPlayer aPlayer) {
+ return mFoodLevel;
+ }
+
+ @Override
+ public float getSaturation(GT_MetaBase_Item aItem, ItemStack aStack, EntityPlayer aPlayer) {
+ return mSaturation;
+ }
+
+ @Override
+ public void onEaten(GT_MetaBase_Item aItem, ItemStack aStack, EntityPlayer aPlayer) {
+ aStack.stackSize--;
+ ItemStack tStack = GT_OreDictUnificator.get(GT_Utility.copyOrNull(mEmptyContainer));
+ if (tStack != null && !aPlayer.inventory.addItemStackToInventory(tStack))
+ aPlayer.dropPlayerItemWithRandomChoice(tStack, true);
+
+ new WorldSpawnedEventBuilder.SoundAtEntityEventBuilder().setIdentifier(SoundResource.RANDOM_BURP)
+ .setVolume(0.5F)
+ .setPitch(aPlayer.worldObj.rand.nextFloat() * 0.1F + 0.9F)
+ .setEntity(aPlayer)
+ .setWorld(aPlayer.worldObj)
+ .run();
+
+ if (!aPlayer.worldObj.isRemote) {
+ if (mMilk) {
+ aPlayer.curePotionEffects(new ItemStack(Items.milk_bucket, 1, 0));
+ }
+ for (int i = 3; i < mPotionEffects.length; i += 4) {
+ if (aPlayer.worldObj.rand.nextInt(100) < mPotionEffects[i]) {
+ aPlayer.addPotionEffect(
+ new PotionEffect(
+ mPotionEffects[i - 3],
+ mPotionEffects[i - 2],
+ mPotionEffects[i - 1],
+ mInvisibleParticles));
+ }
+ }
+ if (mExplosive) {
+ new WorldSpawnedEventBuilder.ExplosionEffectEventBuilder().setSmoking(true)
+ .setFlaming(true)
+ .setStrength(4f)
+ .setPosition(aPlayer.posX, aPlayer.posY, aPlayer.posZ)
+ .setEntity(aPlayer)
+ .setWorld(aPlayer.worldObj)
+ .run();
+ aPlayer.attackEntityFrom(GT_DamageSources.getExplodingDamage(), Float.MAX_VALUE);
+ }
+ }
+ }
+
+ @Override
+ public EnumAction getFoodAction(GT_MetaBase_Item aItem, ItemStack aStack) {
+ return mAction;
+ }
+
+ @Override
+ public boolean alwaysEdible(GT_MetaBase_Item aItem, ItemStack aStack, EntityPlayer aPlayer) {
+ return mAlwaysEdible;
+ }
+
+ @Override
+ public boolean isRotten(GT_MetaBase_Item aItem, ItemStack aStack, EntityPlayer aPlayer) {
+ return mIsRotten;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Forestry_Compat.java b/src/main/java/gregtech/api/util/GT_Forestry_Compat.java
new file mode 100644
index 0000000000..427703e6f7
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Forestry_Compat.java
@@ -0,0 +1,195 @@
+package gregtech.api.util;
+
+import java.util.Map;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import forestry.api.recipes.ICentrifugeRecipe;
+import forestry.api.recipes.ISqueezerRecipe;
+import forestry.api.recipes.RecipeManagers;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.recipe.RecipeMaps;
+
+public class GT_Forestry_Compat {
+
+ public static void populateFakeNeiRecipes() {
+ if (ItemList.FR_Bee_Drone.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Bee_Drone.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Bee_Drone.getWithName(1L, "Scanned Drone") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Bee_Princess.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Bee_Princess.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Bee_Princess.getWithName(1L, "Scanned Princess") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Bee_Queen.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Bee_Queen.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Bee_Queen.getWithName(1L, "Scanned Queen") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Tree_Sapling.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Tree_Sapling.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Tree_Sapling.getWithName(1L, "Scanned Sapling") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Butterfly.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Butterfly.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Butterfly.getWithName(1L, "Scanned Butterfly") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Larvae.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Larvae.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Larvae.getWithName(1L, "Scanned Larvae") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Serum.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Serum.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Serum.getWithName(1L, "Scanned Serum") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_Caterpillar.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_Caterpillar.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_Caterpillar.getWithName(1L, "Scanned Caterpillar") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ if (ItemList.FR_PollenFertile.get(1L) != null) {
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { ItemList.FR_PollenFertile.getWildcard(1L) },
+ new ItemStack[] { ItemList.FR_PollenFertile.getWithName(1L, "Scanned Pollen") },
+ null,
+ new FluidStack[] { Materials.Honey.getFluid(100L) },
+ null,
+ 500,
+ 2,
+ 0);
+ }
+ }
+
+ public static void transferCentrifugeRecipes() {
+ try {
+ for (ICentrifugeRecipe tRecipe : RecipeManagers.centrifugeManager.recipes()) {
+ Map<ItemStack, Float> outputs = tRecipe.getAllProducts();
+ ItemStack[] tOutputs = new ItemStack[outputs.size()];
+ int[] tChances = new int[outputs.size()];
+ int i = 0;
+ for (Map.Entry<ItemStack, Float> entry : outputs.entrySet()) {
+ tChances[i] = (int) (entry.getValue() * 10000);
+ tOutputs[i] = entry.getKey()
+ .copy();
+ i++;
+ }
+ RecipeMaps.centrifugeRecipes.addRecipe(
+ true,
+ new ItemStack[] { tRecipe.getInput() },
+ tOutputs,
+ null,
+ tChances,
+ null,
+ null,
+ 128,
+ 5,
+ 0);
+ RecipeMaps.centrifugeNonCellRecipes.addRecipe(
+ true,
+ new ItemStack[] { tRecipe.getInput() },
+ tOutputs,
+ null,
+ tChances,
+ null,
+ null,
+ 128,
+ 5,
+ 0);
+ }
+ } catch (Throwable e) {
+ if (GT_Values.D1) {
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+ }
+
+ public static void transferSqueezerRecipes() {
+ try {
+ for (ISqueezerRecipe tRecipe : RecipeManagers.squeezerManager.recipes()) {
+ if ((tRecipe.getResources().length == 1) && (tRecipe.getFluidOutput() != null)) {
+ RecipeMaps.fluidExtractionRecipes.addRecipe(
+ true,
+ new ItemStack[] { tRecipe.getResources()[0] },
+ new ItemStack[] { tRecipe.getRemnants() },
+ null,
+ new int[] { (int) (tRecipe.getRemnantsChance() * 10000) },
+ null,
+ new FluidStack[] { tRecipe.getFluidOutput() },
+ 32,
+ 8,
+ 0);
+ }
+ }
+ } catch (Throwable e) {
+ if (GT_Values.D1) {
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_GC_Compat.java b/src/main/java/gregtech/api/util/GT_GC_Compat.java
new file mode 100644
index 0000000000..24710ab0ac
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_GC_Compat.java
@@ -0,0 +1,52 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.Mods.GalacticraftCore;
+
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import micdoodle8.mods.galacticraft.api.power.EnergySource;
+import micdoodle8.mods.galacticraft.api.power.EnergySource.EnergySourceAdjacent;
+import micdoodle8.mods.galacticraft.api.power.IEnergyHandlerGC;
+import micdoodle8.mods.galacticraft.api.transmission.NetworkType;
+import micdoodle8.mods.galacticraft.api.transmission.tile.IConnector;
+import micdoodle8.mods.galacticraft.core.energy.EnergyConfigHandler;
+
+public class GT_GC_Compat {
+
+ public static long insertEnergyInto(TileEntity tTileEntity, long aVoltage, ForgeDirection tDirection) {
+ // GC Compat
+ if (GalacticraftCore.isModLoaded() && tTileEntity instanceof IEnergyHandlerGC) {
+ if (!(tTileEntity instanceof IConnector)
+ || ((IConnector) tTileEntity).canConnect(tDirection, NetworkType.POWER)) {
+ EnergySource eSource = new EnergySourceAdjacent(tDirection);
+
+ float tSizeToReceive = aVoltage * EnergyConfigHandler.IC2_RATIO,
+ tStored = ((IEnergyHandlerGC) tTileEntity).getEnergyStoredGC(eSource);
+ if (tSizeToReceive >= tStored
+ || tSizeToReceive <= ((IEnergyHandlerGC) tTileEntity).getMaxEnergyStoredGC(eSource) - tStored) {
+ float tReceived = ((IEnergyHandlerGC) tTileEntity).receiveEnergyGC(eSource, tSizeToReceive, false);
+ if (tReceived > 0) {
+ tSizeToReceive -= tReceived;
+ while (tSizeToReceive > 0) {
+ tReceived = ((IEnergyHandlerGC) tTileEntity)
+ .receiveEnergyGC(eSource, tSizeToReceive, false);
+ if (tReceived < 1) break;
+ tSizeToReceive -= tReceived;
+ }
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ return 2;
+ }
+
+ public static boolean canConnect(TileEntity tTileEntity, ForgeDirection tDirection) {
+ // GC Compat
+ return GalacticraftCore.isModLoaded() && tTileEntity instanceof IEnergyHandlerGC
+ && (!(tTileEntity instanceof IConnector)
+ || ((IConnector) tTileEntity).canConnect(tDirection, NetworkType.POWER));
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_HatchElementBuilder.java b/src/main/java/gregtech/api/util/GT_HatchElementBuilder.java
new file mode 100644
index 0000000000..2087ad755c
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_HatchElementBuilder.java
@@ -0,0 +1,524 @@
+package gregtech.api.util;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChatComponentTranslation;
+import net.minecraft.util.IChatComponent;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
+import com.gtnewhorizon.structurelib.structure.IItemSource;
+import com.gtnewhorizon.structurelib.structure.IStructureElement;
+import com.gtnewhorizon.structurelib.structure.IStructureElementChain;
+import com.gtnewhorizon.structurelib.structure.IStructureElementNoPlacement;
+import com.gtnewhorizon.structurelib.structure.StructureUtility;
+import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
+
+import gnu.trove.TIntCollection;
+import gnu.trove.list.array.TIntArrayList;
+import gnu.trove.set.hash.TIntHashSet;
+import gregtech.api.interfaces.IHatchElement;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.common.blocks.GT_Item_Machines;
+
+public class GT_HatchElementBuilder<T> {
+
+ private interface Builtin {
+ }
+
+ private IGT_HatchAdder<? super T> mAdder;
+ private int mCasingIndex = -1;
+ private int mDot = -1;
+ private BiPredicate<? super T, ? super IGregTechTileEntity> mShouldSkip;
+ private BiFunction<? super T, ItemStack, ? extends Predicate<ItemStack>> mHatchItemFilter;
+ private Supplier<String> mHatchItemType;
+ private Predicate<? super T> mReject;
+ private boolean mCacheHint;
+ private boolean mNoStop;
+ private EnumSet<ForgeDirection> mDisallowedDirection = EnumSet.noneOf(ForgeDirection.class);
+
+ private GT_HatchElementBuilder() {}
+
+ public static <T> GT_HatchElementBuilder<T> builder() {
+ return new GT_HatchElementBuilder<>();
+ }
+
+ // region composite
+
+ /**
+ * Set all of adder, hint and hatchItemFilter. Provide a reasonable default for shouldSkip. TODO add doc
+ */
+ @SafeVarargs
+ public final GT_HatchElementBuilder<T> anyOf(IHatchElement<? super T>... elements) {
+ if (elements == null || elements.length == 0) throw new IllegalArgumentException();
+ return adder(
+ Arrays.stream(elements)
+ .map(
+ e -> e.adder()
+ .rebrand())
+ .reduce(IGT_HatchAdder::orElse)
+ .get()).hatchClasses(
+ Arrays.stream(elements)
+ .map(IHatchElement::mteClasses)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList()))
+ .cacheHint(
+ () -> Arrays.stream(elements)
+ .map(IHatchElement::name)
+ .sorted()
+ .collect(Collectors.joining(" or ", "of type ", "")));
+ }
+
+ /**
+ * Set all of adder, hint and hatchItemFilter. Provide a reasonable default for shouldSkip.
+ * <p>
+ * Will rotate through all elements TODO add doc
+ */
+ @SafeVarargs
+ public final GT_HatchElementBuilder<T> atLeast(IHatchElement<? super T>... elements) {
+ if (elements == null || elements.length == 0) throw new IllegalArgumentException();
+ return atLeast(
+ Arrays.stream(elements)
+ .collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting())));
+ }
+
+ /**
+ * Set all of adder, hint and hatchItemFilter. Provide a reasonable default for shouldSkip.
+ * <p>
+ * Will rotate through all elements TODO add doc
+ */
+ public final GT_HatchElementBuilder<T> atLeastList(List<IHatchElement<? super T>> elements) {
+ if (elements == null || elements.isEmpty()) throw new IllegalArgumentException();
+ return atLeast(
+ elements.stream()
+ .collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting())));
+ }
+
+ /**
+ * Set all of adder, hint and hatchItemFilter. Provide a reasonable default for shouldSkip. TODO add doc
+ */
+ public final GT_HatchElementBuilder<T> atLeast(Map<IHatchElement<? super T>, ? extends Number> elements) {
+ if (elements == null || elements.isEmpty() || elements.containsKey(null) || elements.containsValue(null))
+ throw new IllegalArgumentException();
+ List<Class<? extends IMetaTileEntity>> list = elements.keySet()
+ .stream()
+ .map(IHatchElement::mteClasses)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ // map cannot be null or empty, so assert Optional isPresent
+ return adder(
+ elements.keySet()
+ .stream()
+ .map(
+ e -> e.adder()
+ .rebrand())
+ .reduce(IGT_HatchAdder::orElse)
+ .orElseThrow(AssertionError::new))
+ .hatchItemFilter(
+ obj -> GT_StructureUtility.filterByMTEClass(
+ elements.entrySet()
+ .stream()
+ .filter(
+ entry -> entry.getKey()
+ .count(obj)
+ < entry.getValue()
+ .longValue())
+ .flatMap(
+ entry -> entry.getKey()
+ .mteClasses()
+ .stream())
+ .collect(Collectors.toList())))
+ .shouldReject(
+ obj -> elements.entrySet()
+ .stream()
+ .allMatch(
+ e -> e.getKey()
+ .count(obj)
+ >= e.getValue()
+ .longValue()))
+ .shouldSkip(
+ (BiPredicate<? super T, ? super IGregTechTileEntity> & Builtin) (c,
+ t) -> t != null && list.stream()
+ .anyMatch(clazz -> clazz.isInstance(t.getMetaTileEntity())))
+ .cacheHint(
+ () -> elements.keySet()
+ .stream()
+ .map(IHatchElement::name)
+ .sorted()
+ .collect(Collectors.joining(" or ", "of type ", "")));
+ }
+ // endregion
+
+ // region primitives
+
+ public GT_HatchElementBuilder<T> adder(IGT_HatchAdder<? super T> aAdder) {
+ if (aAdder == null) throw new IllegalArgumentException();
+ mAdder = aAdder;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> casingIndex(int aCasingIndex) {
+ if (aCasingIndex <= 0) throw new IllegalArgumentException();
+ mCasingIndex = aCasingIndex;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> dot(int aDot) {
+ if (aDot <= 0) throw new IllegalArgumentException();
+ mDot = aDot;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> shouldSkip(BiPredicate<? super T, ? super IGregTechTileEntity> aShouldSkip) {
+ if (!(aShouldSkip instanceof Builtin) || mShouldSkip != null) {
+ if (!(mShouldSkip instanceof Builtin) && mShouldSkip != null) throw new IllegalStateException();
+ if (aShouldSkip == null) throw new IllegalArgumentException();
+ }
+ mShouldSkip = aShouldSkip;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> shouldReject(Predicate<? super T> aShouldReject) {
+ if (aShouldReject == null) throw new IllegalArgumentException();
+ mReject = aShouldReject;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> hatchItemFilter(
+ Function<? super T, ? extends Predicate<ItemStack>> aHatchItemFilter) {
+ if (aHatchItemFilter == null) throw new IllegalArgumentException();
+ mHatchItemFilter = (t, s) -> aHatchItemFilter.apply(t);
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> hatchItemFilterAnd(
+ Function<? super T, ? extends Predicate<ItemStack>> aHatchItemFilter) {
+ if (aHatchItemFilter == null) throw new IllegalArgumentException();
+ BiFunction<? super T, ItemStack, ? extends Predicate<ItemStack>> tOldFilter = mHatchItemFilter;
+ mHatchItemFilter = (t, s) -> tOldFilter.apply(t, s)
+ .and(aHatchItemFilter.apply(t));
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> hatchItemFilter(
+ BiFunction<? super T, ItemStack, ? extends Predicate<ItemStack>> aHatchItemFilter) {
+ if (aHatchItemFilter == null) throw new IllegalArgumentException();
+ mHatchItemFilter = aHatchItemFilter;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> hatchItemFilterAnd(
+ BiFunction<? super T, ItemStack, ? extends Predicate<ItemStack>> aHatchItemFilter) {
+ if (aHatchItemFilter == null) throw new IllegalArgumentException();
+ BiFunction<? super T, ItemStack, ? extends Predicate<ItemStack>> tOldFilter = mHatchItemFilter;
+ mHatchItemFilter = (t, s) -> tOldFilter.apply(t, s)
+ .and(aHatchItemFilter.apply(t, s));
+ return this;
+ }
+
+ // region hint
+ public GT_HatchElementBuilder<T> hint(Supplier<String> aSupplier) {
+ if (aSupplier == null) throw new IllegalArgumentException();
+ mHatchItemType = aSupplier;
+ mCacheHint = false;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> cacheHint(Supplier<String> aSupplier) {
+ if (aSupplier == null) throw new IllegalArgumentException();
+ mHatchItemType = aSupplier;
+ mCacheHint = true;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> cacheHint() {
+ if (mHatchItemType == null) throw new IllegalStateException();
+ mCacheHint = true;
+ return this;
+ }
+ // endregion
+
+ public GT_HatchElementBuilder<T> continueIfSuccess() {
+ mNoStop = true;
+ return this;
+ }
+
+ public GT_HatchElementBuilder<T> stopIfSuccess() {
+ mNoStop = false;
+ return this;
+ }
+
+ /**
+ * Help automatic hatch side determination code by ruling out some directions. Note the automatic hatch side
+ * determination code will choose to use the default facing if the final allowed facing set is empty.
+ * <p>
+ * This will clear the sides set by previous call to this or {@link #allowOnly(ForgeDirection...)}
+ * <p>
+ * Usually mandatory for multis with multiple slices, and otherwise not needed if it contains a single slice only.
+ *
+ * @param facings disallowed direction in ABC coordinate system
+ */
+ public GT_HatchElementBuilder<T> disallowOnly(ForgeDirection... facings) {
+ if (facings == null) throw new IllegalArgumentException();
+ mDisallowedDirection = EnumSet.copyOf(Arrays.asList(facings));
+ return this;
+ }
+
+ /**
+ * Help automatic hatch side determination code by allowing only some directions. Note the automatic hatch side
+ * determination code will choose to use the default facing if the final allowed facing set is empty.
+ * <p>
+ * This will clear the sides set by previous call to this or {@link #disallowOnly(ForgeDirection...)}
+ * <p>
+ * Usually mandatory for multis with multiple slices, and otherwise not needed if it contains a single slice only.
+ *
+ * @param facings allowed direction in ABC coordinate system
+ */
+ public GT_HatchElementBuilder<T> allowOnly(ForgeDirection... facings) {
+ if (facings == null) throw new IllegalArgumentException();
+ mDisallowedDirection = EnumSet.complementOf(EnumSet.copyOf(Arrays.asList(facings)));
+ mDisallowedDirection.remove(ForgeDirection.UNKNOWN);
+ return this;
+ }
+ // endregion
+
+ // region intermediate
+ public GT_HatchElementBuilder<T> hatchClass(Class<? extends IMetaTileEntity> clazz) {
+ return hatchItemFilter(c -> is -> clazz.isInstance(GT_Item_Machines.getMetaTileEntity(is)))
+ .cacheHint(() -> "of class " + clazz.getSimpleName())
+ .shouldSkip(
+ (BiPredicate<? super T, ? super IGregTechTileEntity> & Builtin) (c, t) -> clazz
+ .isInstance(t.getMetaTileEntity()));
+ }
+
+ @SafeVarargs
+ public final GT_HatchElementBuilder<T> hatchClasses(Class<? extends IMetaTileEntity>... classes) {
+ return hatchClasses(Arrays.asList(classes));
+ }
+
+ public final GT_HatchElementBuilder<T> hatchClasses(List<? extends Class<? extends IMetaTileEntity>> classes) {
+ List<? extends Class<? extends IMetaTileEntity>> list = new ArrayList<>(classes);
+ return hatchItemFilter(obj -> GT_StructureUtility.filterByMTEClass(list)).cacheHint(
+ () -> list.stream()
+ .map(Class::getSimpleName)
+ .sorted()
+ .collect(Collectors.joining(" or ", "of class ", "")))
+ .shouldSkip(
+ (BiPredicate<? super T, ? super IGregTechTileEntity> & Builtin) (c, t) -> t != null && list.stream()
+ .anyMatch(clazz -> clazz.isInstance(t.getMetaTileEntity())));
+ }
+
+ public GT_HatchElementBuilder<T> hatchId(int aId) {
+ return hatchItemFilter(
+ c -> is -> GT_Utility.isStackValid(is) && is.getItem() instanceof GT_Item_Machines
+ && is.getItemDamage() == aId).cacheHint(() -> "of id " + aId)
+ .shouldSkip(
+ (BiPredicate<? super T, ? super IGregTechTileEntity> & Builtin) (c, t) -> t != null
+ && t.getMetaTileID() == aId);
+ }
+
+ public GT_HatchElementBuilder<T> hatchIds(int... aIds) {
+ if (aIds == null || aIds.length == 0) throw new IllegalArgumentException();
+ if (aIds.length == 1) return hatchId(aIds[0]);
+ TIntCollection coll = aIds.length < 16 ? new TIntArrayList(aIds) : new TIntHashSet(aIds);
+ return hatchItemFilter(
+ c -> is -> GT_Utility.isStackValid(is) && is.getItem() instanceof GT_Item_Machines
+ && coll.contains(is.getItemDamage())).cacheHint(
+ () -> Arrays.stream(coll.toArray())
+ .sorted()
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(" or ", "of id ", "")))
+ .shouldSkip(
+ (BiPredicate<? super T, ? super IGregTechTileEntity> & Builtin) (c, t) -> t != null
+ && coll.contains(t.getMetaTileID()));
+ }
+
+ // endregion
+
+ @SuppressWarnings("unchecked")
+ @SafeVarargs
+ public final IStructureElementChain<T> buildAndChain(IStructureElement<T>... elements) {
+ List<IStructureElement<T>> l = new ArrayList<>();
+ l.add(build());
+ l.addAll(Arrays.asList(elements));
+ IStructureElement<T>[] array = l.toArray(new IStructureElement[0]);
+ return () -> array;
+ }
+
+ public final IStructureElementChain<T> buildAndChain(Block block, int meta) {
+ return buildAndChain(ofBlock(block, meta));
+ }
+
+ public IStructureElement<T> build() {
+ if (mAdder == null || mCasingIndex == -1 || mDot == -1) {
+ throw new IllegalArgumentException();
+ }
+ if (mHatchItemFilter == null) {
+ // no item filter -> no placement
+ return new IStructureElementNoPlacement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ return tileEntity instanceof IGregTechTileEntity
+ && mAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) mCasingIndex);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), mDot - 1);
+ return true;
+ }
+ };
+ }
+ return new IStructureElement<>() {
+
+ private String mHint = mHatchItemType == null ? "unspecified GT hatch" : mHatchItemType.get();
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ return tileEntity instanceof IGregTechTileEntity
+ && mAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) mCasingIndex);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), mDot - 1);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int i, int i1, int i2, ItemStack itemStack) {
+ // TODO
+ return false;
+ }
+
+ private String getHint() {
+ if (mHint != null) return mHint;
+ String tHint = mHatchItemType.get();
+ if (tHint == null) return "?";
+ // TODO move this to some .lang instead of half ass it into the crappy gt lang file
+ tHint = GT_LanguageManager.addStringLocalization("Hatch_Type_" + tHint.replace(' ', '_'), tHint);
+ if (mCacheHint) {
+ mHint = tHint;
+ if (mHint != null)
+ // yeet the getter, since its product is retrieved and cached
+ mHatchItemType = null;
+ }
+ return tHint;
+ }
+
+ @Override
+ public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ return BlocksToPlace.create(mHatchItemFilter.apply(t, trigger));
+ }
+
+ @Deprecated
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ return survivalPlaceBlock(
+ t,
+ world,
+ x,
+ y,
+ z,
+ trigger,
+ AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ if (mShouldSkip != null) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (tileEntity instanceof IGregTechTileEntity
+ && mShouldSkip.test(t, (IGregTechTileEntity) tileEntity)) return PlaceResult.SKIP;
+ }
+ if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, env.getActor()))
+ return PlaceResult.REJECT;
+ if (mReject != null && mReject.test(t)) return PlaceResult.REJECT;
+ ItemStack taken = env.getSource()
+ .takeOne(mHatchItemFilter.apply(t, trigger), true);
+ if (GT_Utility.isStackInvalid(taken)) {
+ String type = getHint();
+ env.getChatter()
+ .accept(new ChatComponentTranslation("GT5U.autoplace.error.no_hatch", type));
+ return PlaceResult.REJECT;
+ }
+ if (StructureUtility.survivalPlaceBlock(
+ taken,
+ ItemStackPredicate.NBTMode.IGNORE,
+ null,
+ true,
+ world,
+ x,
+ y,
+ z,
+ env.getSource(),
+ env.getActor()) != PlaceResult.ACCEPT) {
+ return PlaceResult.REJECT;
+ }
+ // try to infer facing
+ EnumSet<ForgeDirection> allowed = EnumSet.noneOf(ForgeDirection.class);
+ // first find which face of block is not contained in structure
+ if (env.getAPILevel() == AutoPlaceEnvironment.APILevel.Legacy) {
+ // a legacy decorator isn't passing down necessary information
+ // in that case, we just assume all facing is allowed
+ allowed.addAll(Arrays.asList(ForgeDirection.VALID_DIRECTIONS));
+ } else {
+ for (ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
+ // as noted on getWorldDirection Y axis should be flipped before use
+ if (env.isContainedInPiece(direction.offsetX, -direction.offsetY, direction.offsetZ)) continue;
+ // explicitly rejected, probably obstructed by another slice
+ if (mDisallowedDirection.contains(direction)) continue;
+ ForgeDirection rotated = env.getFacing()
+ .getWorldDirection(
+ (direction.flag & (ForgeDirection.UP.flag | ForgeDirection.DOWN.flag)) != 0
+ ? direction.getOpposite()
+ : direction);
+ allowed.add(rotated);
+ }
+ }
+ if (!allowed.isEmpty()) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (tileEntity instanceof IGregTechTileEntity) {
+ ForgeDirection result = null;
+ // find the first facing available, but prefer a facing that isn't up/down
+ for (ForgeDirection facing : allowed) {
+ result = facing;
+ if ((facing.flag & (ForgeDirection.UP.flag | ForgeDirection.DOWN.flag)) == 0) break; // Horizontal
+ }
+ assert result != null;
+ ((IGregTechTileEntity) tileEntity).setFrontFacing(result);
+ }
+ }
+ return mNoStop ? PlaceResult.ACCEPT : PlaceResult.ACCEPT_STOP;
+ }
+ };
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_IBoxableWrapper.java b/src/main/java/gregtech/api/util/GT_IBoxableWrapper.java
new file mode 100644
index 0000000000..796699c261
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_IBoxableWrapper.java
@@ -0,0 +1,13 @@
+package gregtech.api.util;
+
+import net.minecraft.item.ItemStack;
+
+import ic2.api.item.IBoxable;
+
+public class GT_IBoxableWrapper implements IBoxable {
+
+ @Override
+ public boolean canBeStoredInToolbox(ItemStack itemstack) {
+ return GT_Utility.isStackInList(itemstack, GT_ModHandler.sBoxableItems);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ItsNotMyFaultException.java b/src/main/java/gregtech/api/util/GT_ItsNotMyFaultException.java
new file mode 100644
index 0000000000..f47a356d7b
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ItsNotMyFaultException.java
@@ -0,0 +1,18 @@
+package gregtech.api.util;
+
+public class GT_ItsNotMyFaultException extends RuntimeException {
+
+ private static final long serialVersionUID = -8752778866486460495L;
+
+ private final String mError;
+
+ public GT_ItsNotMyFaultException(String aError) {
+ mError = aError;
+ }
+
+ @Override
+ public String toString() {
+ return "The GregTech-Addon has a Problem.\nIT'S NOT MY FAULT!!! Below is how to fix it.\n" + mError
+ + "\nDO NOT COME TO ME WITH THIS CRASH. YOU CAUSED IT YOURSELF, AND I TOLD YOU HOW TO FIX IT!!!";
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_JubilanceMegaApiary.java b/src/main/java/gregtech/api/util/GT_JubilanceMegaApiary.java
new file mode 100644
index 0000000000..f20a58c34a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_JubilanceMegaApiary.java
@@ -0,0 +1,23 @@
+package gregtech.api.util;
+
+import forestry.api.apiculture.IAlleleBeeSpecies;
+import forestry.api.apiculture.IBeeGenome;
+import forestry.api.apiculture.IBeeHousing;
+import forestry.api.apiculture.IJubilanceProvider;
+
+public class GT_JubilanceMegaApiary implements IJubilanceProvider {
+
+ public static final GT_JubilanceMegaApiary instance = new GT_JubilanceMegaApiary();
+
+ protected GT_JubilanceMegaApiary() {}
+
+ @Override
+ public boolean isJubilant(IAlleleBeeSpecies species, IBeeGenome genome, IBeeHousing housing) {
+ return false;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Will only be produced in mega Apiary";
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_LanguageManager.java b/src/main/java/gregtech/api/util/GT_LanguageManager.java
new file mode 100644
index 0000000000..b1bd45476a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_LanguageManager.java
@@ -0,0 +1,600 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.common.config.Property;
+
+import cpw.mods.fml.common.registry.LanguageRegistry;
+import cpw.mods.fml.relauncher.ReflectionHelper;
+import gregtech.api.GregTech_API;
+
+public class GT_LanguageManager {
+
+ /**
+ * Buffer to reduce memory allocation when injecting data to LanguageRegistry.
+ */
+ private static final HashMap<String, String> TEMPMAP = new HashMap<>();
+ /**
+ * Buffer used when something is trying to add new lang entry while config file is not set up yet.
+ */
+ public static final Map<String, String> BUFFERMAP = new HashMap<>();
+ /**
+ * Map containing all the translation data coming into this class.
+ */
+ private static final Map<String, String> LANGMAP = new HashMap<>();
+ /**
+ * Config file handler bound to GregTech.lang or GregTech_(locale_name).lang. Even though it says English file,
+ * it's not necessarily English, but on system it's always treated as English (as in, "default" language.)
+ */
+ public static Configuration sEnglishFile;
+ /**
+ * If the game is running with en_US language. This does not get updated when user changes language in game;
+ * GT lang system cannot handle that anyway.
+ */
+ public static boolean isEN_US;
+ /**
+ * If placeholder like %material should be used for writing lang entries to file.
+ */
+ public static boolean i18nPlaceholder = true;
+ /**
+ * If there's any lang entry that is not found on lang file and waiting to be written.
+ */
+ private static boolean hasUnsavedEntry = false;
+
+ // TODO: convert to enum
+ public static String FACE_ANY = "gt.lang.face.any", FACE_BOTTOM = "gt.lang.face.bottom",
+ FACE_TOP = "gt.lang.face.top", FACE_LEFT = "gt.lang.face.left", FACE_FRONT = "gt.lang.face.front",
+ FACE_RIGHT = "gt.lang.face.right", FACE_BACK = "gt.lang.face.back", FACE_NONE = "gt.lang.face.none";
+
+ public static String[] FACES = { FACE_BOTTOM, FACE_TOP, FACE_LEFT, FACE_FRONT, FACE_RIGHT, FACE_BACK, FACE_NONE };
+
+ /**
+ * Map referencing private field of StringTranslate, used by StatCollector. Used to inject lang entries there.
+ */
+ private static final Map<String, String> stringTranslateLanguageList;
+
+ static {
+ try {
+ Field fieldStringTranslateLanguageList = ReflectionHelper
+ .findField(net.minecraft.util.StringTranslate.class, "languageList", "field_74816_c");
+ Field fieldStringTranslateInstance = ReflectionHelper
+ .findField(net.minecraft.util.StringTranslate.class, "instance", "field_74817_a");
+ // noinspection unchecked
+ stringTranslateLanguageList = (Map<String, String>) fieldStringTranslateLanguageList
+ .get(fieldStringTranslateInstance.get(null));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @deprecated Parameter aWriteIntoLangFile is no longer used,
+ * use {@link #addStringLocalization(String, String)} or consider migrating to MC lang system instead.
+ */
+ @Deprecated
+ public static synchronized String addStringLocalization(String aKey, String aEnglish, boolean aWriteIntoLangFile) {
+ return addStringLocalization(aKey, aEnglish);
+ }
+
+ /**
+ * If you newly use this method, please consider using MC lang system instead.
+ */
+ public static synchronized String addStringLocalization(String aKey, String aEnglish) {
+ String trimmedKey = aKey != null ? aKey.trim() : "";
+ if (trimmedKey.isEmpty()) return E; // RIP cascading class loading, don't use GT_Utility here
+ if (sEnglishFile == null) {
+ // Lang file is not set up yet
+ BUFFERMAP.put(trimmedKey, aEnglish);
+ return aEnglish;
+ }
+ if (!BUFFERMAP.isEmpty()) {
+ // Lang file is now set up, resolve all the buffers
+ // This won't be visited twice
+ for (Entry<String, String> tEntry : BUFFERMAP.entrySet()) {
+ storeTranslation(tEntry.getKey(), tEntry.getValue());
+ }
+ BUFFERMAP.clear();
+ }
+
+ if (!LANGMAP.containsKey(trimmedKey)) {
+ return storeTranslation(trimmedKey, aEnglish);
+ }
+ return LANGMAP.get(trimmedKey);
+ }
+
+ private static synchronized String storeTranslation(String trimmedKey, String english) {
+ String translation = writeToLangFile(trimmedKey, english);
+ LANGMAP.put(trimmedKey, translation);
+ addToMCLangList(trimmedKey, translation);
+ TEMPMAP.put(trimmedKey, translation);
+ LanguageRegistry.instance()
+ // If we use the actual user configured locale here, switching lang to others while running game
+ // turns everything into unlocalized string. So we make it "default" and call it a day.
+ .injectLanguage("en_US", TEMPMAP);
+ TEMPMAP.clear();
+ return translation;
+ }
+
+ private static synchronized String writeToLangFile(String trimmedKey, String aEnglish) {
+ Property tProperty = sEnglishFile.get("LanguageFile", trimmedKey, aEnglish);
+ if (hasUnsavedEntry && GregTech_API.sPostloadFinished) {
+ sEnglishFile.save();
+ hasUnsavedEntry = false;
+ }
+ String translation = tProperty.getString();
+ if (tProperty.wasRead()) {
+ if (isEN_US && !aEnglish.equals(translation)) {
+ tProperty.set(aEnglish);
+ markFileDirty();
+ return aEnglish;
+ }
+ } else {
+ markFileDirty();
+ }
+ return translation;
+ }
+
+ private static synchronized void markFileDirty() {
+ if (GregTech_API.sPostloadFinished) {
+ sEnglishFile.save();
+ } else {
+ hasUnsavedEntry = true;
+ }
+ }
+
+ public static String getTranslation(String aKey) {
+ String tTrimmedKey = aKey != null ? aKey.trim() : "";
+ if (tTrimmedKey.isEmpty()) return E;
+
+ if (StatCollector.canTranslate(tTrimmedKey)) {
+ return StatCollector.translateToLocal(tTrimmedKey);
+ }
+ String anotherKeyToTry;
+ if (tTrimmedKey.endsWith(".name")) {
+ anotherKeyToTry = tTrimmedKey.substring(0, tTrimmedKey.length() - 5);
+ } else {
+ anotherKeyToTry = tTrimmedKey + ".name";
+ }
+ if (StatCollector.canTranslate(anotherKeyToTry)) {
+ return StatCollector.translateToLocal(anotherKeyToTry);
+ }
+ return tTrimmedKey;
+ }
+
+ public static String getTranslation(String aKey, String aSeperator) {
+ if (aKey == null) return E;
+ String rTranslation = E;
+ StringBuilder rTranslationSB = new StringBuilder(rTranslation);
+ for (String tString : aKey.split(aSeperator)) {
+ rTranslationSB.append(getTranslation(tString));
+ }
+ rTranslation = String.valueOf(rTranslationSB);
+ return rTranslation;
+ }
+
+ @SuppressWarnings("unused")
+ public static String getTranslateableItemStackName(ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return "null";
+ NBTTagCompound tNBT = aStack.getTagCompound();
+ if (tNBT != null && tNBT.hasKey("display")) {
+ String tName = tNBT.getCompoundTag("display")
+ .getString("Name");
+ if (GT_Utility.isStringValid(tName)) {
+ return tName;
+ }
+ }
+ return aStack.getUnlocalizedName() + ".name";
+ }
+
+ public static void writePlaceholderStrings() {
+ addStringLocalization("Interaction_DESCRIPTION_Index_001", "Puts out into adjacent Slot #");
+ addStringLocalization("Interaction_DESCRIPTION_Index_002", "Grabs in for own Slot #");
+ addStringLocalization("Interaction_DESCRIPTION_Index_003", "Enable with Signal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_004", "Disable with Signal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_005", "Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_006", "Export");
+ addStringLocalization("Interaction_DESCRIPTION_Index_007", "Import");
+ addStringLocalization("Interaction_DESCRIPTION_Index_008", "Export (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_009", "Import (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_010", "Export (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_011", "Import (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_012", "Export allow Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_013", "Import allow Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_014", "Export allow Input (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_015", "Import allow Output (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_016", "Export allow Input (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_017", "Import allow Output (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_018", "Normal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_019", "Inverted");
+ addStringLocalization("Interaction_DESCRIPTION_Index_020", "Ready to work");
+ addStringLocalization("Interaction_DESCRIPTION_Index_021", "Not ready to work");
+ addStringLocalization("Interaction_DESCRIPTION_Index_022", "Import");
+ addStringLocalization("Interaction_DESCRIPTION_Index_023", "Import (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_024", "Import (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_025", "Keep Liquids Away");
+ addStringLocalization("Interaction_DESCRIPTION_Index_026", "Keep Liquids Away (conditional)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_027", "Keep Liquids Away (invert cond)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_031", "Normal Universal Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_032", "Inverted Universal Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_033", "Normal Electricity Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_034", "Inverted Electricity Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_035", "Normal Steam Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_036", "Inverted Steam Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_037", "Normal Average Electric Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_038", "Inverted Average Electric Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_039", "Normal Average Electric Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_040", "Inverted Average Electric Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_041", "Normal Electricity Storage(Including Batteries)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_042", "Inverted Electricity Storage(Including Batteries)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_043", "Filter input, Deny output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_044", "Invert input, Deny output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_045", "Filter input, Permit any output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_046", "Invert input, Permit any output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_047", "Filter Fluid: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_048", "Pump speed: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_049", "L/tick ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_050", "L/sec");
+ addStringLocalization("Interaction_DESCRIPTION_Index_053", "Slot: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_054", "Inverted");
+ addStringLocalization("Interaction_DESCRIPTION_Index_055", "Normal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_056", "Emit if 1 Maintenance Needed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_057", "Emit if 1 Maintenance Needed(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_058", "Emit if 2 Maintenance Needed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_059", "Emit if 2 Maintenance Needed(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_060", "Emit if 3 Maintenance Needed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_061", "Emit if 3 Maintenance Needed(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_062", "Emit if 4 Maintenance Needed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_063", "Emit if 4 Maintenance Needed(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_064", "Emit if 5 Maintenance Needed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_065", "Emit if 5 Maintenance Needed(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_066", "Emit if rotor needs maintenance low accuracy mod");
+ addStringLocalization(
+ "Interaction_DESCRIPTION_Index_067",
+ "Emit if rotor needs maintenance low accuracy mod(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_068", "Emit if rotor needs maintenance high accuracy mod");
+ addStringLocalization("Interaction_DESCRIPTION_Index_068.1", "Emit if any Player is close");
+ addStringLocalization(
+ "Interaction_DESCRIPTION_Index_069",
+ "Emit if rotor needs maintenance high accuracy mod(inverted)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_069.1", "Emit if other Player is close");
+ addStringLocalization("Interaction_DESCRIPTION_Index_070", "Emit if you are close");
+ addStringLocalization("Interaction_DESCRIPTION_Index_071", "Conducts strongest Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_072", "Conducts from bottom Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_073", "Conducts from top Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_074", "Conducts from north Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_075", "Conducts from south Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_076", "Conducts from west Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_077", "Conducts from east Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_078", "Signal = ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_079", "Conditional Signal = ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_080", "Inverted Conditional Signal = ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_081", "Frequency: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_082", "Open if work enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_083", "Open if work disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_084", "Only Output allowed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_085", "Only Input allowed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_086", "Auto-Input: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_087", "Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_088", "Enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_089", " Auto-Output: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_090", "Machine Processing: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_091", "Redstone Output at Side ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_092", " set to: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_093", "Strong");
+ addStringLocalization("Interaction_DESCRIPTION_Index_094", "Weak");
+ addStringLocalization("Interaction_DESCRIPTION_Index_094.1", "Not enough soldering material!");
+ addStringLocalization("Interaction_DESCRIPTION_Index_095", "Input from Output Side allowed");
+ addStringLocalization("Interaction_DESCRIPTION_Index_096", "Input from Output Side forbidden");
+ addStringLocalization("Interaction_DESCRIPTION_Index_098", "Do not regulate Item Stack Size");
+ addStringLocalization("Interaction_DESCRIPTION_Index_099", "Regulate Item Stack Size to: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_100", "This is ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_101", " Ore.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_102", "There is Lava behind this Rock.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_103", "There is a Liquid behind this Rock.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_104", "There is an Air Pocket behind this Rock.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_105", "Material is changing behind this Rock.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_106", "Found traces of ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_107", "No Ores found.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_108", "Outputs misc. Fluids, Steam and Items");
+ addStringLocalization("Interaction_DESCRIPTION_Index_109", "Outputs Steam and Items");
+ addStringLocalization("Interaction_DESCRIPTION_Index_110", "Outputs Steam and misc. Fluids");
+ addStringLocalization("Interaction_DESCRIPTION_Index_111", "Outputs Steam");
+ addStringLocalization("Interaction_DESCRIPTION_Index_112", "Outputs misc. Fluids and Items");
+ addStringLocalization("Interaction_DESCRIPTION_Index_113", "Outputs only Items");
+ addStringLocalization("Interaction_DESCRIPTION_Index_114", "Outputs only misc. Fluids");
+ addStringLocalization("Interaction_DESCRIPTION_Index_115", "Outputs nothing");
+ // 116 moved to lang files
+ // 117 obsolete
+ // 118 moved to lang files
+ // 119 obsolete
+ // 120 moved to lang files
+ // 121 obsolete
+ addStringLocalization("Interaction_DESCRIPTION_Index_122", "Emit Redstone if slots contain something");
+ addStringLocalization("Interaction_DESCRIPTION_Index_123", "Don't emit Redstone");
+ // 124 moved to lang files
+ addStringLocalization("Interaction_DESCRIPTION_Index_124.1", "Blacklist Mode");
+ // 125 obsolete
+ addStringLocalization("Interaction_DESCRIPTION_Index_125.1", "Whitelist Mode");
+ // 126 moved to lang files
+ // 127 obsolete
+ addStringLocalization("Interaction_DESCRIPTION_Index_128", "Redstone");
+ addStringLocalization("Interaction_DESCRIPTION_Index_128.1", "Redstone ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_129", "Energy");
+ addStringLocalization("Interaction_DESCRIPTION_Index_129.1", "Energy ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_130", "Fluids");
+ addStringLocalization("Interaction_DESCRIPTION_Index_130.1", "Fluids ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_131", "Items");
+ addStringLocalization("Interaction_DESCRIPTION_Index_131.1", "Items ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_132", "Pipe is loose.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_133", "Screws are loose.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_134", "Something is stuck.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_135", "Platings are dented.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_136", "Circuitry burned out.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_137", "That doesn't belong there.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_138", "Incomplete Structure.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_139", "Hit with Soft Mallet");
+ addStringLocalization("Interaction_DESCRIPTION_Index_140", "to (re-)start the Machine");
+ addStringLocalization("Interaction_DESCRIPTION_Index_141", "if it doesn't start.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_142", "Running perfectly.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_143", "Missing Mining Pipe");
+ addStringLocalization("Interaction_DESCRIPTION_Index_144", "Missing Turbine Rotor");
+ addStringLocalization("Interaction_DESCRIPTION_Index_145", "Step Down, In: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_146", "Step Up, In: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_147", "A, Out: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_148", "V ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_149", "A");
+ addStringLocalization("Interaction_DESCRIPTION_Index_150", "Chance: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_151", "Does not get consumed in the process");
+ addStringLocalization("Interaction_DESCRIPTION_Index_151.1", "Outputs items and 1 specific Fluid");
+ addStringLocalization("Interaction_DESCRIPTION_Index_151.2", "Outputs 1 specific Fluid");
+ addStringLocalization("Interaction_DESCRIPTION_Index_151.4", "Successfully locked Fluid to %s");
+ addStringLocalization("Interaction_DESCRIPTION_Index_152", "Total: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_153", "Usage: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_154", "Voltage: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_155", "Amperage: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_156", "Voltage: unspecified");
+ addStringLocalization("Interaction_DESCRIPTION_Index_157", "Amperage: unspecified");
+ addStringLocalization("Interaction_DESCRIPTION_Index_158", "Time: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_159", "Needs Low Gravity");
+ addStringLocalization("Interaction_DESCRIPTION_Index_160", "Needs Cleanroom");
+ addStringLocalization("Interaction_DESCRIPTION_Index_160.1", "Needs Cleanroom & LowGrav");
+ addStringLocalization("Interaction_DESCRIPTION_Index_161", " secs");
+ addStringLocalization("Interaction_DESCRIPTION_Index_162", "Name: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_163", " MetaData: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_164", "Hardness: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_165", " Blast Resistance: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_166", "Is valid Beacon Pyramid Material");
+ addStringLocalization("Interaction_DESCRIPTION_Index_167", "Tank ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_168", "Heat: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_169", "HEM: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_170", " Base EU Output: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_171", "Facing: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_172", " / Chance: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_173", "You can remove this with a Wrench");
+ addStringLocalization("Interaction_DESCRIPTION_Index_174", "You can NOT remove this with a Wrench");
+ addStringLocalization("Interaction_DESCRIPTION_Index_175", "Conduction Loss: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_176", "Contained Energy: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_177", "Has Muffler Upgrade");
+ addStringLocalization("Interaction_DESCRIPTION_Index_178", "Progress/Load: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_179", "Max IN: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_181", "Max OUT: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_182", " EU at ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_183", " A");
+ addStringLocalization("Interaction_DESCRIPTION_Index_184", "Energy: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_186", "Owned by: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_187", "Type -- Crop-Name: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_188", " Growth: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_189", " Gain: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_190", " Resistance: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_191", "Plant -- Fertilizer: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_192", " Water: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_193", " Weed-Ex: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_194", " Scan-Level: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_195", "Environment -- Nutrients: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_196", " Humidity: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_197", " Air-Quality: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_198", "Attributes:");
+ addStringLocalization("Interaction_DESCRIPTION_Index_199", "Discovered by: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_200", "Sort mode: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_200.1", "Automatic Item Shuffling: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_201", "Nothing");
+ addStringLocalization("Interaction_DESCRIPTION_Index_202", "Pollution in Chunk: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_203", " gibbl");
+ addStringLocalization("Interaction_DESCRIPTION_Index_204", "No Pollution in Chunk! HAYO!");
+ addStringLocalization("Interaction_DESCRIPTION_Index_206", "Scan for Assembly Line");
+ addStringLocalization(
+ "Interaction_DESCRIPTION_Index_207",
+ "Pump speed: %dL every %d ticks, %.2f L/sec on average");
+ addStringLocalization("Interaction_DESCRIPTION_Index_208", " L");
+ addStringLocalization("Interaction_DESCRIPTION_Index_209", " ticks");
+ addStringLocalization("Interaction_DESCRIPTION_Index_209.1", " tick");
+ addStringLocalization("Interaction_DESCRIPTION_Index_210", "Average: %.2f L/sec");
+ addStringLocalization("Interaction_DESCRIPTION_Index_211", "Items per side: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_212", "Input enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_213", "Input disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_214", "Connected");
+ addStringLocalization("Interaction_DESCRIPTION_Index_215", "Disconnected");
+ addStringLocalization("Interaction_DESCRIPTION_Index_216", "Deprecated Recipe");
+ addStringLocalization("Interaction_DESCRIPTION_Index_219", "Extended Facing: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_220", "Single recipe locking disabled.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_221", "Item threshold");
+ addStringLocalization("Interaction_DESCRIPTION_Index_222", "Fluid threshold");
+ addStringLocalization("Interaction_DESCRIPTION_Index_222.1", "Energy threshold");
+ addStringLocalization(
+ "Interaction_DESCRIPTION_Index_223",
+ "Single recipe locking enabled. Will lock to next recipe.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_224", "Always On");
+ addStringLocalization("Interaction_DESCRIPTION_Index_225", "Active with Redstone Signal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_226", "Inactive with Redstone Signal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_227", "Allow Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_228", "Block Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_229", "Export/Import");
+ addStringLocalization("Interaction_DESCRIPTION_Index_230", "Conditional");
+ addStringLocalization("Interaction_DESCRIPTION_Index_231", "Enable Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_232", "Filter Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_233", "Filter Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_234", "Block Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_235", "Allow Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_236", "Whitelist Fluid");
+ addStringLocalization("Interaction_DESCRIPTION_Index_237", "Blacklist Fluid");
+ addStringLocalization("Interaction_DESCRIPTION_Index_238", "Filter Direction");
+ addStringLocalization("Interaction_DESCRIPTION_Index_239", "Filter Type");
+ addStringLocalization("Interaction_DESCRIPTION_Index_240", "Block Flow");
+ addStringLocalization("Interaction_DESCRIPTION_Index_241", "Recipe progress");
+ addStringLocalization("Interaction_DESCRIPTION_Index_242", "Machine idle");
+ addStringLocalization("Interaction_DESCRIPTION_Index_243", "Enable with Redstone");
+ addStringLocalization("Interaction_DESCRIPTION_Index_244", "Disable with Redstone");
+ addStringLocalization("Interaction_DESCRIPTION_Index_245", "Disable machine");
+ addStringLocalization("Interaction_DESCRIPTION_Index_246", "Frequency");
+ addStringLocalization("Interaction_DESCRIPTION_Index_247", "1 Issue");
+ addStringLocalization("Interaction_DESCRIPTION_Index_248", "2 Issues");
+ addStringLocalization("Interaction_DESCRIPTION_Index_249", "3 Issues");
+ addStringLocalization("Interaction_DESCRIPTION_Index_250", "4 Issues");
+ addStringLocalization("Interaction_DESCRIPTION_Index_251", "5 Issues");
+ addStringLocalization("Interaction_DESCRIPTION_Index_252", "Rotor < 80%");
+ addStringLocalization("Interaction_DESCRIPTION_Index_253", "Rotor < 100%");
+ addStringLocalization("Interaction_DESCRIPTION_Index_254", "Detect slot#");
+ addStringLocalization("Interaction_DESCRIPTION_Index_254.0", "Detect Slot");
+ addStringLocalization("Interaction_DESCRIPTION_Index_254.1", "Internal slot#");
+ addStringLocalization("Interaction_DESCRIPTION_Index_255", "Adjacent slot#");
+ addStringLocalization("Interaction_DESCRIPTION_Index_256", "Universal Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_257", "Electricity Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_258", "Steam Storage");
+ addStringLocalization("Interaction_DESCRIPTION_Index_259", "Average Electric Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_260", "Average Electric Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_261", "Electricity Storage(Including Batteries)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_262", "Fluid Auto Output Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_263", "Fluid Auto Output Enabled");
+ addStringLocalization(
+ "Interaction_DESCRIPTION_Index_264",
+ "currently none, will be locked to the next that is put in");
+ addStringLocalization("Interaction_DESCRIPTION_Index_265", "1 specific Fluid");
+ addStringLocalization("Interaction_DESCRIPTION_Index_266", "Lock Fluid Mode Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_267", "Overflow Voiding Mode Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_268", "Overflow Voiding Mode Enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_269", "Void Full Mode Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_270", "Void Full Mode Enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_271", "unspecified");
+ addStringLocalization("Interaction_DESCRIPTION_Index_272", "Recipe by: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_273", "Original Recipe by: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_274", "Modified by: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_275", "Original voltage: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_299", "Item Filter: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_300", "Filter Cleared!");
+ addStringLocalization("Interaction_DESCRIPTION_Index_300.1", "Fluid Lock Cleared.");
+ addStringLocalization("Interaction_DESCRIPTION_Index_301", "Universal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_302", "Int. EU");
+ addStringLocalization("Interaction_DESCRIPTION_Index_303", "Steam");
+ addStringLocalization("Interaction_DESCRIPTION_Index_304", "Avg. Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_305", "Avg. Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_306", "EU stored");
+ addStringLocalization("Interaction_DESCRIPTION_Index_307", "Deny input, Filter output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_308", "Deny input, Invert output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_309", "Permit any input, Filter output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_310", "Permit any input, Invert output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_311", "Block Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_312", "Allow Output");
+ addStringLocalization("Interaction_DESCRIPTION_Index_313", "Block Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_314", "Allow Input");
+ addStringLocalization("Interaction_DESCRIPTION_Index_315", "Filter Empty");
+ addStringLocalization("Interaction_DESCRIPTION_Index_316", "Pump speed limit reached!");
+ addStringLocalization("Interaction_DESCRIPTION_Index_317", "Filter: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_318", "Check Mode");
+ addStringLocalization("Interaction_DESCRIPTION_Index_319", "Any player");
+ addStringLocalization("Interaction_DESCRIPTION_Index_320", "Other players");
+ addStringLocalization("Interaction_DESCRIPTION_Index_321", "Only owner");
+ addStringLocalization("Interaction_DESCRIPTION_Index_322", "Overflow point: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_323", "L");
+ addStringLocalization("Interaction_DESCRIPTION_Index_324", "Now");
+ addStringLocalization("Interaction_DESCRIPTION_Index_325", "Max");
+ addStringLocalization("Interaction_DESCRIPTION_Index_326", "Public");
+ addStringLocalization("Interaction_DESCRIPTION_Index_327", "Private");
+ addStringLocalization("Interaction_DESCRIPTION_Index_328", "Channel");
+ addStringLocalization("Interaction_DESCRIPTION_Index_329", "Public/Private");
+ addStringLocalization("Interaction_DESCRIPTION_Index_330", "Sneak Rightclick to switch Mode");
+ addStringLocalization("Interaction_DESCRIPTION_Index_331", "AND Gate");
+ addStringLocalization("Interaction_DESCRIPTION_Index_332", "NAND Gate");
+ addStringLocalization("Interaction_DESCRIPTION_Index_333", "OR Gate");
+ addStringLocalization("Interaction_DESCRIPTION_Index_334", "NOR Gate");
+ addStringLocalization("Interaction_DESCRIPTION_Index_335", "Gate Mode");
+ addStringLocalization("Interaction_DESCRIPTION_Index_336", "PCB Factory Tier: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_337", "Upgrade Required: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_338", "Bio");
+ addStringLocalization("Interaction_DESCRIPTION_Index_339", "Biochamber Upgrade Enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_339.1", "Biochamber Upgrade Disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_340", "Rotated biochamber enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_340.1", "Rotated biochamber disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_341", "Tier 1 cooling enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_341.1", "Tier 1 cooling disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_342", "Tier 2 cooling enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_342.1", "Tier 2 cooling disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_343", "Use Machine Processing State");
+ addStringLocalization("Interaction_DESCRIPTION_Index_343.1", "Use Inverted Machine Processing State");
+ addStringLocalization("Interaction_DESCRIPTION_Index_344", "Input Blocking");
+ addStringLocalization("Interaction_DESCRIPTION_Index_344.1", "Output Blocking");
+ addStringLocalization("Interaction_DESCRIPTION_Index_500", "Fitting: Loose - More Flow");
+ addStringLocalization("Interaction_DESCRIPTION_Index_501", "Fitting: Tight - More Efficiency");
+ addStringLocalization("Interaction_DESCRIPTION_Index_502", "Mining chunk loading enabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_503", "Mining chunk loading disabled");
+ addStringLocalization("Interaction_DESCRIPTION_Index_505", "Enable with Signal (Safe)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_506", "Disable with Signal (Safe)");
+ addStringLocalization("Interaction_DESCRIPTION_Index_507", "Safe Mode");
+ addStringLocalization("Interaction_DESCRIPTION_Index_602", "Use Private Frequency");
+ addStringLocalization("Interaction_DESCRIPTION_Index_756", "Connectable: ");
+ addStringLocalization("Interaction_DESCRIPTION_Index_ALL", "All");
+ addStringLocalization("Interaction_DESCRIPTION_Index_ANY", "Any");
+ addStringLocalization("Interaction_DESCRIPTION_Index_INVERTED", "Inverted");
+ addStringLocalization("Interaction_DESCRIPTION_Index_NORMAL", "Normal");
+ addStringLocalization("Interaction_DESCRIPTION_Index_SIDE", "Side: ");
+
+ addStringLocalization("Item_DESCRIPTION_Index_000", "Stored Heat: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_001", "Durability: %s/%s");
+ addStringLocalization("Item_DESCRIPTION_Index_002", "%s lvl %s");
+ addStringLocalization("Item_DESCRIPTION_Index_003", "Attack Damage: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_004", "Mining Speed: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_005", "Turbine Efficiency: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_006", "Optimal Steam flow: %s L/t");
+ addStringLocalization("Item_DESCRIPTION_Index_007", "Energy from Optimal Gas Flow: %s EU/t");
+ addStringLocalization("Item_DESCRIPTION_Index_008", "Energy from Optimal Plasma Flow: %s EU/t");
+ addStringLocalization("Item_DESCRIPTION_Index_009", "Contains %s EU Tier: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_010", "Empty. You should recycle it properly.");
+ addStringLocalization("Item_DESCRIPTION_Index_011", "%s / %s EU - Voltage: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_012", "No Fluids Contained");
+ addStringLocalization("Item_DESCRIPTION_Index_013", "%sL / %sL");
+ addStringLocalization("Item_DESCRIPTION_Index_014", "Missing Coodinates!");
+ addStringLocalization("Item_DESCRIPTION_Index_015", "Device at:");
+ addStringLocalization("Item_DESCRIPTION_Index_018", "State: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_019", "Bath with neutron in a hot reactor");
+ addStringLocalization("Item_DESCRIPTION_Index_020", "Progress: %s/%s");
+ addStringLocalization("Item_DESCRIPTION_Index_021", "Radiation Hazard");
+ addStringLocalization("Item_DESCRIPTION_Index_500", "Turbine Efficiency (Loose): %s");
+ addStringLocalization("Item_DESCRIPTION_Index_501", "Optimal Steam flow (Loose): %s L/t");
+ addStringLocalization("Item_DESCRIPTION_Index_502", "Overflow Efficiency Tier: %s");
+ addStringLocalization("Item_DESCRIPTION_Index_900", "Energy from Optimal Steam Flow: %s EU/t");
+ addStringLocalization("Item_DESCRIPTION_Index_901", "Energy from Optimal Steam Flow (Loose): %s EU/t");
+
+ addStringLocalization(FACE_ANY, "Any Side");
+ addStringLocalization(FACE_BOTTOM, "Bottom");
+ addStringLocalization(FACE_TOP, "Top");
+ addStringLocalization(FACE_LEFT, "Left");
+ addStringLocalization(FACE_FRONT, "Front");
+ addStringLocalization(FACE_RIGHT, "Right");
+ addStringLocalization(FACE_BACK, "Back");
+ addStringLocalization(FACE_NONE, "None");
+ }
+
+ private static void addToMCLangList(String aKey, String translation) {
+ if (stringTranslateLanguageList != null) {
+ stringTranslateLanguageList.put(aKey, translation);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Log.java b/src/main/java/gregtech/api/util/GT_Log.java
new file mode 100644
index 0000000000..2d00c2e061
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Log.java
@@ -0,0 +1,45 @@
+package gregtech.api.util;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * NEVER INCLUDE THIS FILE IN YOUR MOD!!!
+ * <p/>
+ * Just a simple Logging Function. If on Server, then this will point to System.out and System.err
+ */
+public class GT_Log {
+
+ public static PrintStream out = System.out;
+ public static PrintStream err = System.err;
+ public static PrintStream ore = new LogBuffer();
+ public static PrintStream pal = null;
+ public static PrintStream exp = new LogBuffer();
+ public static File mLogFile;
+ public static File mOreDictLogFile;
+ public static File mPlayerActivityLogFile;
+ public static File mExplosionLog;
+
+ public static class LogBuffer extends PrintStream {
+
+ public final List<String> mBufferedOreDictLog = new ArrayList<>();
+
+ public LogBuffer() {
+ super(new OutputStream() {
+
+ @Override
+ public void write(int arg0) {
+ /* Do nothing */
+ }
+ });
+ }
+
+ @Override
+ public void println(String aString) {
+ mBufferedOreDictLog.add(aString);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ModHandler.java b/src/main/java/gregtech/api/util/GT_ModHandler.java
new file mode 100644
index 0000000000..70dc2f30b0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ModHandler.java
@@ -0,0 +1,2551 @@
+package gregtech.api.util;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.enums.GT_Values.B;
+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.M;
+import static gregtech.api.enums.GT_Values.RA;
+import static gregtech.api.enums.GT_Values.V;
+import static gregtech.api.enums.GT_Values.W;
+import static gregtech.api.recipe.RecipeMaps.alloySmelterRecipes;
+import static gregtech.api.recipe.RecipeMaps.extractorRecipes;
+import static gregtech.api.recipe.RecipeMaps.oreWasherRecipes;
+import static gregtech.api.util.GT_RecipeBuilder.SECONDS;
+import static gregtech.api.util.GT_RecipeBuilder.TICKS;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.inventory.Container;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.CraftingManager;
+import net.minecraft.item.crafting.FurnaceRecipes;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.item.crafting.ShapedRecipes;
+import net.minecraft.item.crafting.ShapelessRecipes;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntityFurnace;
+import net.minecraft.world.World;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.oredict.ShapedOreRecipe;
+import net.minecraftforge.oredict.ShapelessOreRecipe;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OreDictNames;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.ToolDictNames;
+import gregtech.api.interfaces.IDamagableItem;
+import gregtech.api.interfaces.IItemContainer;
+import gregtech.api.interfaces.internal.IGT_CraftingRecipe;
+import gregtech.api.items.GT_MetaBase_Item;
+import gregtech.api.objects.GT_HashSet;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.objects.ItemData;
+import gregtech.api.recipe.RecipeCategories;
+import gregtech.api.recipe.RecipeMap;
+import ic2.api.item.IBoxable;
+import ic2.api.item.IC2Items;
+import ic2.api.item.IElectricItem;
+import ic2.api.reactor.IReactorComponent;
+import ic2.api.recipe.IRecipeInput;
+import ic2.api.recipe.RecipeInputItemStack;
+import ic2.api.recipe.RecipeOutput;
+import ic2.core.item.ItemToolbox;
+
+/**
+ * NEVER INCLUDE THIS FILE IN YOUR MOD!!!
+ * <p/>
+ * This is the Interface I use for interacting with other Mods.
+ * <p/>
+ * Due to the many imports, this File can cause compile Problems if not all the APIs are installed
+ */
+public class GT_ModHandler {
+
+ public static final List<IRecipe> sSingleNonBlockDamagableRecipeList = new ArrayList<>(1000);
+ private static final Map<String, ItemStack> sIC2ItemMap = new HashMap<>();
+
+ private static final List<IRecipe> sAllRecipeList = new ArrayList<>(5000),
+ sBufferRecipeList = new ArrayList<>(1000);
+ private static final List<ItemStack> delayedRemovalByOutput = new ArrayList<>();
+ private static final List<InventoryCrafting> delayedRemovalByRecipe = new ArrayList<>();
+
+ public static Collection<String> sNativeRecipeClasses = new HashSet<>(), sSpecialRecipeClasses = new HashSet<>();
+ public static GT_HashSet<GT_ItemStack> sNonReplaceableItems = new GT_HashSet<>();
+ public static Object sBoxableWrapper = new GT_IBoxableWrapper();
+ public static Collection<GT_ItemStack> sBoxableItems = new ArrayList<>();
+ private static final Map<IRecipeInput, RecipeOutput> emptyRecipeMap = new HashMap<>();
+ private static Set<GT_Utility.ItemId> recyclerWhitelist;
+ private static Set<GT_Utility.ItemId> recyclerBlacklist;
+
+ private static boolean sBufferCraftingRecipes = true;
+ public static List<Integer> sSingleNonBlockDamagableRecipeList_list = new ArrayList<>(100);
+ private static final boolean sSingleNonBlockDamagableRecipeList_create = true;
+ private static final ItemStack sMt1 = new ItemStack(Blocks.dirt, 1, 0), sMt2 = new ItemStack(Blocks.dirt, 1, 0);
+ private static final String s_H = "h", s_F = "f", s_I = "I", s_P = "P", s_R = "R";
+ private static final ItemStack[][] sShapes1 = new ItemStack[][] {
+ { sMt1, null, sMt1, sMt1, sMt1, sMt1, null, sMt1, null },
+ { sMt1, null, sMt1, sMt1, null, sMt1, sMt1, sMt1, sMt1 },
+ { null, sMt1, null, sMt1, sMt1, sMt1, sMt1, null, sMt1 },
+ { sMt1, sMt1, sMt1, sMt1, null, sMt1, null, null, null },
+ { sMt1, null, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1 },
+ { sMt1, sMt1, sMt1, sMt1, null, sMt1, sMt1, null, sMt1 },
+ { null, null, null, sMt1, null, sMt1, sMt1, null, sMt1 },
+ { null, sMt1, null, null, sMt1, null, null, sMt2, null },
+ { sMt1, sMt1, sMt1, null, sMt2, null, null, sMt2, null },
+ { null, sMt1, null, null, sMt2, null, null, sMt2, null },
+ { sMt1, sMt1, null, sMt1, sMt2, null, null, sMt2, null },
+ { null, sMt1, sMt1, null, sMt2, sMt1, null, sMt2, null },
+ { sMt1, sMt1, null, null, sMt2, null, null, sMt2, null },
+ { null, sMt1, sMt1, null, sMt2, null, null, sMt2, null },
+ { null, sMt1, null, sMt1, null, null, null, sMt1, sMt2 },
+ { null, sMt1, null, null, null, sMt1, sMt2, sMt1, null },
+ { null, sMt1, null, sMt1, null, sMt1, null, null, sMt2 },
+ { null, sMt1, null, sMt1, null, sMt1, sMt2, null, null },
+ { null, sMt2, null, null, sMt1, null, null, sMt1, null },
+ { null, sMt2, null, null, sMt2, null, sMt1, sMt1, sMt1 },
+ { null, sMt2, null, null, sMt2, null, null, sMt1, null },
+ { null, sMt2, null, sMt1, sMt2, null, sMt1, sMt1, null },
+ { null, sMt2, null, null, sMt2, sMt1, null, sMt1, sMt1 },
+ { null, sMt2, null, null, sMt2, null, sMt1, sMt1, null },
+ { sMt1, null, null, null, sMt2, null, null, null, sMt2 },
+ { null, null, sMt1, null, sMt2, null, sMt2, null, null },
+ { sMt1, null, null, null, sMt2, null, null, null, null },
+ { null, null, sMt1, null, sMt2, null, null, null, null },
+ { sMt1, sMt2, null, null, null, null, null, null, null },
+ { sMt2, sMt1, null, null, null, null, null, null, null },
+ { sMt1, null, null, sMt2, null, null, null, null, null },
+ { sMt2, null, null, sMt1, null, null, null, null, null },
+ { sMt1, sMt1, sMt1, sMt1, sMt1, sMt1, null, sMt2, null },
+ { sMt1, sMt1, null, sMt1, sMt1, sMt2, sMt1, sMt1, null },
+ { null, sMt1, sMt1, sMt2, sMt1, sMt1, null, sMt1, sMt1 },
+ { null, sMt2, null, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1 },
+ { sMt1, sMt1, sMt1, sMt1, sMt2, sMt1, null, sMt2, null },
+ { sMt1, sMt1, null, sMt1, sMt2, sMt2, sMt1, sMt1, null },
+ { null, sMt1, sMt1, sMt2, sMt2, sMt1, null, sMt1, sMt1 },
+ { null, sMt2, null, sMt1, sMt2, sMt1, sMt1, sMt1, sMt1 },
+ { sMt1, null, null, null, sMt1, null, null, null, null },
+ { null, sMt1, null, sMt1, null, null, null, null, null },
+ { sMt1, sMt1, null, sMt2, null, sMt1, sMt2, null, null },
+ { null, sMt1, sMt1, sMt1, null, sMt2, null, null, sMt2 } };
+ public static List<Integer> sSingleNonBlockDamagableRecipeList_validsShapes1 = new ArrayList<>(44);
+ public static boolean sSingleNonBlockDamagableRecipeList_validsShapes1_update = false;
+ public static List<Integer> sSingleNonBlockDamagableRecipeList_warntOutput = new ArrayList<>(50);
+ public static List<Integer> sVanillaRecipeList_warntOutput = new ArrayList<>(50);
+ public static final List<IRecipe> sSingleNonBlockDamagableRecipeList_verified = new ArrayList<>(1000);
+ public static List<Integer> sAnySteamFluidIDs = new ArrayList<>();
+ public static List<Integer> sSuperHeatedSteamFluidIDs = new ArrayList<>();
+
+ static {
+ sNativeRecipeClasses.add(ShapedRecipes.class.getName());
+ sNativeRecipeClasses.add(ShapedOreRecipe.class.getName());
+ sNativeRecipeClasses.add(GT_Shaped_Recipe.class.getName());
+ sNativeRecipeClasses.add(ShapelessRecipes.class.getName());
+ sNativeRecipeClasses.add(ShapelessOreRecipe.class.getName());
+ sNativeRecipeClasses.add(GT_Shapeless_Recipe.class.getName());
+ sNativeRecipeClasses.add(ic2.core.AdvRecipe.class.getName());
+ sNativeRecipeClasses.add(ic2.core.AdvShapelessRecipe.class.getName());
+ sNativeRecipeClasses.add("appeng.recipes.game.ShapedRecipe");
+ sNativeRecipeClasses.add("appeng.recipes.game.ShapelessRecipe");
+ sNativeRecipeClasses.add("forestry.core.utils.ShapedRecipeCustom");
+
+ // Recipe Classes, which should never be removed.
+ sSpecialRecipeClasses.add(net.minecraft.item.crafting.RecipeFireworks.class.getName());
+ sSpecialRecipeClasses.add(net.minecraft.item.crafting.RecipesArmorDyes.class.getName());
+ sSpecialRecipeClasses.add(net.minecraft.item.crafting.RecipeBookCloning.class.getName());
+ sSpecialRecipeClasses.add(net.minecraft.item.crafting.RecipesMapCloning.class.getName());
+ sSpecialRecipeClasses.add(net.minecraft.item.crafting.RecipesMapExtending.class.getName());
+ sSpecialRecipeClasses.add("jds.bibliocraft.BiblioSpecialRecipes");
+ sSpecialRecipeClasses.add("dan200.qcraft.shared.EntangledQBlockRecipe");
+ sSpecialRecipeClasses.add("dan200.qcraft.shared.EntangledQuantumComputerRecipe");
+ sSpecialRecipeClasses.add("dan200.qcraft.shared.QBlockRecipe");
+ sSpecialRecipeClasses.add("appeng.recipes.game.FacadeRecipe");
+ sSpecialRecipeClasses.add("appeng.recipes.game.DisassembleRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.carts.LocomotivePaintingRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.util.crafting.RotorRepairRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.util.crafting.RoutingTableCopyRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.util.crafting.RoutingTicketCopyRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.util.crafting.TankCartFilterRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.emblems.LocomotiveEmblemRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.emblems.EmblemPostColorRecipe");
+ sSpecialRecipeClasses.add("mods.railcraft.common.emblems.EmblemPostEmblemRecipe");
+ sSpecialRecipeClasses.add("mods.immibis.redlogic.interaction.RecipeDyeLumarButton");
+ sSpecialRecipeClasses.add("thaumcraft.common.items.armor.RecipesRobeArmorDyes");
+ sSpecialRecipeClasses.add("thaumcraft.common.items.armor.RecipesVoidRobeArmorDyes");
+ sSpecialRecipeClasses.add("thaumcraft.common.lib.crafting.ShapelessNBTOreRecipe");
+ sSpecialRecipeClasses.add("twilightforest.item.TFMapCloningRecipe");
+ sSpecialRecipeClasses.add("forestry.lepidopterology.MatingRecipe");
+ sSpecialRecipeClasses.add("micdoodle8.mods.galacticraft.planets.asteroids.recipe.CanisterRecipes");
+ sSpecialRecipeClasses.add("shedar.mods.ic2.nuclearcontrol.StorageArrayRecipe");
+ }
+
+ /**
+ * Returns if that Liquid is Water or Distilled Water
+ */
+ public static boolean isWater(FluidStack aFluid) {
+ if (aFluid == null) return false;
+ return aFluid.isFluidEqual(getWater(1)) || aFluid.isFluidEqual(getDistilledWater(1));
+ }
+
+ /**
+ * Returns a Liquid Stack with given amount of Water.
+ */
+ public static FluidStack getWater(long aAmount) {
+ return FluidRegistry.getFluidStack("water", (int) aAmount);
+ }
+
+ /**
+ * Returns a Liquid Stack with given amount of distilled Water.
+ */
+ public static FluidStack getDistilledWater(long aAmount) {
+ FluidStack tFluid = FluidRegistry.getFluidStack("ic2distilledwater", (int) aAmount);
+ if (tFluid == null) tFluid = getWater(aAmount);
+ return tFluid;
+ }
+
+ /**
+ * Returns if that Liquid is Lava
+ */
+ public static boolean isLava(FluidStack aFluid) {
+ if (aFluid == null) return false;
+ return aFluid.isFluidEqual(getLava(1));
+ }
+
+ /**
+ * Returns a Liquid Stack with given amount of Lava.
+ */
+ public static FluidStack getLava(long aAmount) {
+ return FluidRegistry.getFluidStack("lava", (int) aAmount);
+ }
+
+ /**
+ * Returns if that Liquid is Steam
+ */
+ public static boolean isSteam(FluidStack aFluid) {
+ if (aFluid == null) return false;
+ return aFluid.isFluidEqual(getSteam(1));
+ }
+
+ /**
+ * Returns if that Liquid is Any Steam (including other mods)
+ */
+ public static boolean isAnySteam(FluidStack aFluid) {
+ return (aFluid != null && (isSteam(aFluid) || sAnySteamFluidIDs.contains(aFluid.getFluidID())));
+ }
+
+ /**
+ * Returns if that Liquid is Super Heated Steam (including other mods)
+ */
+ public static boolean isSuperHeatedSteam(FluidStack aFluid) {
+ return (aFluid != null && sSuperHeatedSteamFluidIDs.contains(aFluid.getFluidID()));
+ }
+
+ /**
+ * Returns a Liquid Stack with given amount of Steam.
+ */
+ public static FluidStack getSteam(long aAmount) {
+ return FluidRegistry.getFluidStack("steam", (int) aAmount);
+ }
+
+ /**
+ * Returns if that Liquid is Milk
+ */
+ public static boolean isMilk(FluidStack aFluid) {
+ if (aFluid == null) return false;
+ return aFluid.isFluidEqual(getMilk(1));
+ }
+
+ /**
+ * Returns a Liquid Stack with given amount of Milk.
+ */
+ public static FluidStack getMilk(long aAmount) {
+ return FluidRegistry.getFluidStack("milk", (int) aAmount);
+ }
+
+ @Deprecated
+ public static ItemStack getEmptyFuelCan(long aAmount) {
+ return null;
+ }
+
+ public static ItemStack getEmptyCell(long aAmount) {
+ return ItemList.Cell_Empty.get(aAmount);
+ }
+
+ public static ItemStack getAirCell(long aAmount) {
+ return ItemList.Cell_Air.get(aAmount);
+ }
+
+ public static ItemStack getWaterCell(long aAmount) {
+ return ItemList.Cell_Water.get(aAmount);
+ }
+
+ public static ItemStack getLavaCell(long aAmount) {
+ return ItemList.Cell_Lava.get(aAmount);
+ }
+
+ /**
+ * @param aValue the Value of this Stack, when burning inside a Furnace (200 = 1 Burn Process = 500 EU, max = 32767
+ * (that is 81917.5 EU)), limited to Short because the vanilla Furnace otherwise can't handle it
+ * properly, stupid Mojang...
+ */
+ public static ItemStack setFuelValue(ItemStack aStack, short aValue) {
+ aStack.setTagCompound(GT_Utility.getNBTContainingShort(aStack.getTagCompound(), "GT.ItemFuelValue", aValue));
+ return aStack;
+ }
+
+ /**
+ * @return the Value of this Stack, when burning inside a Furnace (200 = 1 Burn Process = 500 EU, max = 32767 (that
+ * is 81917.5 EU)), limited to Short because the vanilla Furnace otherwise can't handle it properly, stupid
+ * Mojang...
+ */
+ public static int getFuelValue(ItemStack aStack) {
+ return TileEntityFurnace.getItemBurnTime(aStack);
+ }
+
+ /**
+ * @param aValue Fuel value in EU
+ */
+ @Deprecated
+ public static ItemStack getFuelCan(int aValue) {
+ return null;
+ }
+
+ /**
+ * @param aFuelCan the Item you want to check
+ * @return the exact Value in EU the Fuel Can is worth if its even a Fuel Can.
+ */
+ @Deprecated
+ public static int getFuelCanValue(ItemStack aFuelCan) {
+ return 0;
+ }
+
+ /**
+ * Gets an Item from IndustrialCraft, and returns a Replacement Item if not possible
+ */
+ public static ItemStack getIC2Item(String aItem, long aAmount, ItemStack aReplacement) {
+ if (GT_Utility.isStringInvalid(aItem) || !GregTech_API.sPreloadStarted) return null;
+ // if (D1) GT_Log.out.println("Requested the Item '" + aItem + "' from the IC2-API");
+ if (!sIC2ItemMap.containsKey(aItem)) try {
+ ItemStack tStack = IC2Items.getItem(aItem);
+ sIC2ItemMap.put(aItem, tStack);
+ if (tStack == null && D1) GT_Log.err.println(aItem + " is not found in the IC2 Items!");
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return GT_Utility.copyAmount(aAmount, sIC2ItemMap.get(aItem), aReplacement);
+ }
+
+ /**
+ * Gets an Item from IndustrialCraft, but the Damage Value can be specified, and returns a Replacement Item with the
+ * same Damage if not possible
+ */
+ public static ItemStack getIC2Item(String aItem, long aAmount, int aMeta, ItemStack aReplacement) {
+ ItemStack rStack = getIC2Item(aItem, aAmount, aReplacement);
+ if (rStack == null) return null;
+ Items.feather.setDamage(rStack, aMeta);
+ return rStack;
+ }
+
+ /**
+ * Gets an Item from IndustrialCraft, but the Damage Value can be specified
+ */
+ public static ItemStack getIC2Item(String aItem, long aAmount, int aMeta) {
+ return getIC2Item(aItem, aAmount, aMeta, null);
+ }
+
+ /**
+ * Gets an Item from IndustrialCraft
+ */
+ public static ItemStack getIC2Item(String aItem, long aAmount) {
+ return getIC2Item(aItem, aAmount, null);
+ }
+
+ /**
+ * Gets an Item from the specified mod
+ */
+ public static ItemStack getModItem(String aModID, String aItem, long aAmount) {
+ return getModItem(aModID, aItem, aAmount, null);
+ }
+
+ /**
+ * Gets an Item from the specified mod, and returns a Replacement Item if not possible
+ */
+ public static ItemStack getModItem(String aModID, String aItem, long aAmount, ItemStack aReplacement) {
+ ItemStack result;
+ if (GT_Utility.isStringInvalid(aItem) || !GregTech_API.sPreloadStarted) {
+ result = null;
+ } else {
+ result = GT_Utility
+ .copyAmount(aAmount, GameRegistry.findItemStack(aModID, aItem, (int) aAmount), aReplacement);
+ }
+
+ if (result == null) {
+ String reason;
+ if (GT_Utility.isStringInvalid(aItem)) {
+ reason = "the name of the item is an invalid string";
+ } else if (!GregTech_API.sPreloadStarted) {
+ reason = "the GT5U preloading phase has not yet started";
+ } else {
+ reason = "the item was not found in the game registry";
+ }
+ String log_message = "getModItem call: object \"" + aItem
+ + "\" with mod id \""
+ + aModID
+ + "\" has returned null because "
+ + reason;
+ GT_Log.out.println(log_message);
+ new Exception().printStackTrace(GT_Log.out);
+ }
+ return result;
+ }
+
+ /**
+ * Gets an Item from the specified mod, but the Damage Value can be specified
+ */
+ public static ItemStack getModItem(String aModID, String aItem, long aAmount, int aMeta) {
+ ItemStack rStack = getModItem(aModID, aItem, aAmount);
+ if (rStack == null) return null;
+ Items.feather.setDamage(rStack, aMeta);
+ return rStack;
+ }
+
+ /**
+ * Gets an Item from the specified mod, but the Damage Value can be specified, and returns a Replacement Item with
+ * the same Damage if not possible
+ */
+ public static ItemStack getModItem(String aModID, String aItem, long aAmount, int aMeta, ItemStack aReplacement) {
+ ItemStack rStack = getModItem(aModID, aItem, aAmount, aReplacement);
+ if (rStack == null) return null;
+ Items.feather.setDamage(rStack, aMeta);
+ return rStack;
+ }
+
+ /**
+ * OUT OF ORDER
+ */
+ public static boolean getModeKeyDown(EntityPlayer aPlayer) {
+ return false;
+ }
+
+ /**
+ * OUT OF ORDER
+ */
+ public static boolean getBoostKeyDown(EntityPlayer aPlayer) {
+ return false;
+ }
+
+ /**
+ * OUT OF ORDER
+ */
+ public static boolean getJumpKeyDown(EntityPlayer aPlayer) {
+ return false;
+ }
+
+ /**
+ * Adds a Valuable Ore to the Miner
+ */
+ public static boolean addValuableOre(Block aBlock, int aMeta, int aValue) {
+ if (aValue <= 0) return false;
+ try {
+ Class.forName("ic2.core.IC2")
+ .getMethod("addValuableOre", IRecipeInput.class, int.class)
+ .invoke(null, new RecipeInputItemStack(new ItemStack(aBlock, 1, aMeta)), aValue);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return true;
+ }
+
+ /**
+ * Adds a Scrapbox Drop. Fails at April first for the "suddenly Hoes"-Feature of IC2
+ */
+ public static boolean addScrapboxDrop(float aChance, ItemStack aOutput) {
+ aOutput = GT_OreDictUnificator.get(true, aOutput);
+ if (aOutput == null || aChance <= 0) return false;
+ aOutput.stackSize = 1;
+ if (GT_Config.troll && !GT_Utility.areStacksEqual(aOutput, new ItemStack(Items.wooden_hoe, 1, 0))) return false;
+ try {
+ GT_Utility.callMethod(
+ GT_Utility.getFieldContent("ic2.api.recipe.Recipes", "scrapboxDrops", true, true),
+ "addDrop",
+ true,
+ false,
+ true,
+ GT_Utility.copyOrNull(aOutput),
+ aChance);
+ GT_Utility.callMethod(
+ GT_Utility.getFieldContent("ic2.api.recipe.Recipes", "scrapboxDrops", true, true),
+ "addRecipe",
+ true,
+ true,
+ false,
+ GT_Utility.copyOrNull(aOutput),
+ aChance);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return true;
+ }
+
+ /**
+ * Adds an Item to the Recycler Blacklist
+ */
+ public static boolean addToRecyclerBlackList(ItemStack aRecycledStack) {
+ if (aRecycledStack == null) return false;
+ try {
+ ic2.api.recipe.Recipes.recyclerBlacklist.add(new RecipeInputItemStack(aRecycledStack));
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return true;
+ }
+
+ /**
+ * Just simple Furnace smelting. Unbelievable how Minecraft fails at making a simple ItemStack->ItemStack mapping...
+ */
+ public static boolean addSmeltingRecipe(ItemStack aInput, ItemStack aOutput) {
+ aOutput = GT_OreDictUnificator.get(true, aOutput);
+ if (aInput == null || aOutput == null) return false;
+ FurnaceRecipes.smelting()
+ .func_151394_a(aInput, GT_Utility.copyOrNull(aOutput), 0.0F);
+ return true;
+ }
+
+ /**
+ * Adds to Furnace AND Alloy Smelter
+ */
+ public static boolean addSmeltingAndAlloySmeltingRecipe(ItemStack aInput, ItemStack aOutput, boolean hidden) {
+ if (aInput == null || aOutput == null) {
+ return false;
+ }
+ boolean temp = aInput.stackSize == 1 && addSmeltingRecipe(aInput, aOutput);
+ ItemStack input2 = OrePrefixes.ingot.contains(aOutput) ? ItemList.Shape_Mold_Ingot.get(0)
+ : OrePrefixes.block.contains(aOutput) ? ItemList.Shape_Mold_Block.get(0)
+ : OrePrefixes.nugget.contains(aOutput) ? ItemList.Shape_Mold_Nugget.get(0) : null;
+ if (Materials.Graphite.contains(aInput)) {
+ return false;
+ }
+ if ((input2 == null) && ((OrePrefixes.ingot.contains(aInput)) || (OrePrefixes.dust.contains(aInput))
+ || (OrePrefixes.gem.contains(aInput)))) {
+ return false;
+ }
+ GT_RecipeBuilder recipeBuilder = GT_Values.RA.stdBuilder();
+ if (input2 == null) {
+ recipeBuilder.itemInputs(aInput);
+ } else {
+ recipeBuilder.itemInputs(aInput, input2);
+ }
+ recipeBuilder.itemOutputs(aOutput)
+ .duration(6 * SECONDS + 10 * TICKS)
+ .eut(3)
+ .recipeCategory(RecipeCategories.alloySmelterRecycling);
+ if (hidden) {
+ recipeBuilder.hidden();
+ }
+ recipeBuilder.addTo(alloySmelterRecipes);
+ return true;
+ }
+
+ /**
+ * LiquidTransposer Recipe for both directions
+ */
+ @Deprecated
+ public static boolean addLiquidTransposerRecipe(ItemStack aEmptyContainer, FluidStack aLiquid,
+ ItemStack aFullContainer, int aMJ) {
+ return true;
+ }
+
+ /**
+ * LiquidTransposer Recipe for filling Containers
+ */
+ @Deprecated
+ public static boolean addLiquidTransposerFillRecipe(ItemStack aEmptyContainer, FluidStack aLiquid,
+ ItemStack aFullContainer, int aMJ) {
+ return true;
+ }
+
+ /**
+ * LiquidTransposer Recipe for emptying Containers
+ */
+ @Deprecated
+ public static boolean addLiquidTransposerEmptyRecipe(ItemStack aFullContainer, FluidStack aLiquid,
+ ItemStack aEmptyContainer, int aMJ) {
+ return true;
+ }
+
+ /**
+ * IC2-Extractor Recipe. Overloads old Recipes automatically
+ */
+ @Deprecated
+ public static boolean addExtractionRecipe(ItemStack aInput, ItemStack aOutput) {
+ aOutput = GT_OreDictUnificator.get(true, aOutput);
+ if (aInput == null || aOutput == null) return false;
+ RA.stdBuilder()
+ .itemInputs(aInput)
+ .itemOutputs(aOutput)
+ .duration(15 * SECONDS)
+ .eut(2)
+ .addTo(extractorRecipes);
+ return true;
+ }
+
+ /**
+ * RC-BlastFurnace Recipes
+ */
+ @Deprecated
+ public static boolean addRCBlastFurnaceRecipe(ItemStack aInput, ItemStack aOutput, int aTime) {
+ return true;
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1) {
+ return addPulverisationRecipe(aInput, aOutput1, null, 0, false);
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2) {
+ return addPulverisationRecipe(aInput, aOutput1, aOutput2, 100, false);
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2,
+ int aChance) {
+ return addPulverisationRecipe(aInput, aOutput1, aOutput2, aChance, false);
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, boolean aOverwrite) {
+ return addPulverisationRecipe(aInput, aOutput1, null, 0, aOverwrite);
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2,
+ boolean aOverwrite) {
+ return addPulverisationRecipe(aInput, aOutput1, aOutput2, 100, aOverwrite);
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2, int aChance,
+ boolean aOverwrite) {
+ return addPulverisationRecipe(aInput, aOutput1, aOutput2, aChance, null, 0, aOverwrite);
+ }
+
+ /**
+ * Adds Several Pulverizer-Type Recipes.
+ */
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2, int aChance2,
+ ItemStack aOutput3, int aChance3, boolean aOverwrite) {
+ aOutput1 = GT_OreDictUnificator.get(true, aOutput1);
+ aOutput2 = GT_OreDictUnificator.get(true, aOutput2);
+ if (GT_Utility.isStackInvalid(aInput) || GT_Utility.isStackInvalid(aOutput1)) return false;
+
+ if (GT_Utility.getContainerItem(aInput, false) == null) {
+ RA.addPulveriserRecipe(
+ aInput,
+ new ItemStack[] { aOutput1, aOutput2, aOutput3 },
+ new int[] { 10000, aChance2 <= 0 ? 1000 : 100 * aChance2, aChance3 <= 0 ? 1000 : 100 * aChance3 },
+ 400,
+ 2);
+ }
+ return true;
+ }
+
+ @Deprecated
+ public static boolean addPulverisationRecipe(ItemStack aInputItem, ItemStack[] aOutputArray, int[] aChanceArray,
+ int aEUt, int aRecipeDurationInTicks) {
+
+ ItemStack[] aUnifiedOutputArray = new ItemStack[aOutputArray.length];
+ int counter = 0;
+
+ for (ItemStack item : aOutputArray) {
+ aUnifiedOutputArray[counter] = GT_OreDictUnificator.get(true, item);
+ counter++;
+ }
+
+ RA.addPulveriserRecipe(aInputItem, aOutputArray, aChanceArray, aRecipeDurationInTicks, aEUt);
+
+ return true;
+ }
+
+ @Deprecated
+ public static boolean addImmersiveEngineeringRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2,
+ int aChance2, ItemStack aOutput3, int aChance3) {
+ return true;
+ }
+
+ @Deprecated
+ public static boolean addMagneticraftRecipe(ItemStack aInput, ItemStack aOutput1, ItemStack aOutput2, int aChance2,
+ ItemStack aOutput3, int aChance3) {
+ return true;
+ }
+
+ /**
+ * Adds a Recipe to the Sawmills of ThermalCraft
+ */
+ @Deprecated
+ public static boolean addSawmillRecipe(ItemStack aInput1, ItemStack aOutput1, ItemStack aOutput2) {
+ return true;
+ }
+
+ /**
+ * Induction Smelter Recipes and Alloy Smelter Recipes
+ */
+ @Deprecated
+ public static boolean addAlloySmelterRecipe(ItemStack aInput1, ItemStack aInput2, ItemStack aOutput1, int aDuration,
+ int aEUt, boolean aAllowSecondaryInputEmpty) {
+ if (aInput1 == null || (aInput2 == null && !aAllowSecondaryInputEmpty) || aOutput1 == null) return false;
+ aOutput1 = GT_OreDictUnificator.get(true, aOutput1);
+ RA.stdBuilder()
+ .itemInputs(aInput1, aInput2)
+ .itemOutputs(aOutput1)
+ .duration(aDuration)
+ .eut(aEUt)
+ .addTo(alloySmelterRecipes);
+ return true;
+ }
+
+ /**
+ * Induction Smelter Recipes for TE
+ */
+ public static boolean addInductionSmelterRecipe(ItemStack aInput1, ItemStack aInput2, ItemStack aOutput1,
+ ItemStack aOutput2, int aEnergy, int aChance) {
+ return true;
+ }
+
+ /**
+ * Smelts Ores to Ingots
+ */
+ public static boolean addOreToIngotSmeltingRecipe(ItemStack aInput, ItemStack aOutput) {
+ aOutput = GT_OreDictUnificator.get(true, aOutput);
+ if (aInput == null || aOutput == null) return false;
+ FurnaceRecipes.smelting()
+ .func_151394_a(aInput, GT_Utility.copyOrNull(aOutput), 0.0F);
+ return true;
+ }
+
+ /**
+ * Adds GT versions of the IC2 recipes from the supplied IC2RecipeList.
+ */
+ public static void addIC2RecipesToGT(Map<IRecipeInput, RecipeOutput> aIC2RecipeList, RecipeMap<?> aGTRecipeMap,
+ boolean aAddGTRecipe, boolean aRemoveIC2Recipe, boolean aExcludeGTIC2Items) {
+ Map<ItemStack, ItemStack> aRecipesToRemove = new HashMap<>();
+ for (Entry<IRecipeInput, RecipeOutput> iRecipeInputRecipeOutputEntry : aIC2RecipeList.entrySet()) {
+ if (iRecipeInputRecipeOutputEntry.getValue().items.isEmpty()) {
+ continue;
+ }
+
+ for (ItemStack tStack : (iRecipeInputRecipeOutputEntry.getKey()).getInputs()) {
+ if (!GT_Utility.isStackValid(tStack)) {
+ continue;
+ }
+
+ if (aAddGTRecipe) {
+ try {
+ if (aExcludeGTIC2Items && ((tStack.getUnlocalizedName()
+ .contains("gt.metaitem.01")
+ || tStack.getUnlocalizedName()
+ .contains("gt.blockores")
+ || tStack.getUnlocalizedName()
+ .contains("ic2.itemCrushed")
+ || tStack.getUnlocalizedName()
+ .contains("ic2.itemPurifiedCrushed"))))
+ continue;
+ switch (aGTRecipeMap.unlocalizedName) {
+ case "gt.recipe.macerator", "gt.recipe.extractor", "gt.recipe.compressor" -> aGTRecipeMap
+ .addRecipe(
+ true,
+ new ItemStack[] { GT_Utility.copyAmount(
+ iRecipeInputRecipeOutputEntry.getKey()
+ .getAmount(),
+ tStack) },
+ iRecipeInputRecipeOutputEntry.getValue().items.toArray(new ItemStack[0]),
+ null,
+ null,
+ null,
+ null,
+ 300,
+ 2,
+ 0);
+ case "gt.recipe.thermalcentrifuge" -> aGTRecipeMap.addRecipe(
+ true,
+ new ItemStack[] { GT_Utility.copyAmount(
+ iRecipeInputRecipeOutputEntry.getKey()
+ .getAmount(),
+ tStack) },
+ iRecipeInputRecipeOutputEntry.getValue().items.toArray(new ItemStack[0]),
+ null,
+ null,
+ null,
+ null,
+ 500,
+ 48,
+ 0);
+ }
+ } catch (Exception e) {
+ System.err.println(e);
+ }
+ }
+ if (aRemoveIC2Recipe) {
+ aRecipesToRemove.put(tStack, iRecipeInputRecipeOutputEntry.getValue().items.get(0));
+ }
+
+ }
+
+ }
+ GT_Utility.bulkRemoveSimpleIC2MachineRecipe(aRecipesToRemove, aIC2RecipeList);
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getExtractorRecipeList() {
+ try {
+ return ic2.api.recipe.Recipes.extractor.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getCompressorRecipeList() {
+ try {
+ return ic2.api.recipe.Recipes.compressor.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getMaceratorRecipeList() {
+ try {
+ return ic2.api.recipe.Recipes.macerator.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getThermalCentrifugeRecipeList() {
+ try {
+ return ic2.api.recipe.Recipes.centrifuge.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getOreWashingRecipeList() {
+ try {
+ return ic2.api.recipe.Recipes.oreWashing.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ public static Map<IRecipeInput, RecipeOutput> getMassFabricatorList() {
+ try {
+ return ic2.api.recipe.Recipes.matterAmplifier.getRecipes();
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return emptyRecipeMap;
+ }
+
+ /**
+ * IC2-ThermalCentrifuge Recipe. Overloads old Recipes automatically
+ */
+ @Deprecated
+ public static boolean addThermalCentrifugeRecipe(ItemStack aInput, int[] aChances, int aHeat, Object... aOutput) {
+ if (aInput == null || aOutput == null || aOutput.length == 0 || aOutput[0] == null) return false;
+ RA.addThermalCentrifugeRecipe(
+ aInput,
+ (ItemStack) aOutput[0],
+ aOutput.length >= 2 ? (ItemStack) aOutput[1] : null,
+ aOutput.length >= 3 ? (ItemStack) aOutput[2] : null,
+ aChances,
+ 500,
+ 48);
+ return true;
+ }
+
+ @Deprecated
+ public static boolean addThermalCentrifugeRecipe(ItemStack aInput, int aHeat, Object... aOutput) {
+ if (aInput == null || aOutput == null || aOutput.length == 0 || aOutput[0] == null) return false;
+ RA.addThermalCentrifugeRecipe(
+ aInput,
+ (ItemStack) aOutput[0],
+ aOutput.length >= 2 ? (ItemStack) aOutput[1] : null,
+ aOutput.length >= 3 ? (ItemStack) aOutput[2] : null,
+ 500,
+ 48);
+ return true;
+ }
+
+ /**
+ * IC2-OreWasher Recipe. Overloads old Recipes automatically
+ */
+ public static boolean addOreWasherRecipe(ItemStack aInput, int[] aChances, int aWaterAmount, Object... aOutput) {
+ if (aInput == null || aOutput == null || aOutput.length == 0 || aOutput[0] == null) return false;
+ RA.stdBuilder()
+ .itemInputs(aInput)
+ .itemOutputs((ItemStack) aOutput[0], (ItemStack) aOutput[1], (ItemStack) aOutput[2])
+ .outputChances(aChances)
+ .fluidInputs(GT_ModHandler.getWater(aWaterAmount))
+ .duration(25 * SECONDS)
+ .eut(16)
+ .addTo(oreWasherRecipes);
+
+ RA.stdBuilder()
+ .itemInputs(aInput)
+ .itemOutputs((ItemStack) aOutput[0], (ItemStack) aOutput[1], (ItemStack) aOutput[2])
+ .outputChances(aChances)
+ .fluidInputs(GT_ModHandler.getDistilledWater(aWaterAmount / 5))
+ .duration(15 * SECONDS)
+ .eut(16)
+ .addTo(oreWasherRecipes);
+ return true;
+ }
+
+ public static boolean addOreWasherRecipe(ItemStack aInput, int aWaterAmount, Object... aOutput) {
+ if (aInput == null || aOutput == null || aOutput.length == 0 || aOutput[0] == null) return false;
+ RA.stdBuilder()
+ .itemInputs(aInput)
+ .itemOutputs((ItemStack) aOutput[0], (ItemStack) aOutput[1], (ItemStack) aOutput[2])
+ .fluidInputs(GT_ModHandler.getWater(aWaterAmount))
+ .duration(25 * SECONDS)
+ .eut(16)
+ .addTo(oreWasherRecipes);
+
+ RA.stdBuilder()
+ .itemInputs(aInput)
+ .itemOutputs((ItemStack) aOutput[0], (ItemStack) aOutput[1], (ItemStack) aOutput[2])
+ .fluidInputs(GT_ModHandler.getDistilledWater(aWaterAmount / 5))
+ .duration(15 * SECONDS)
+ .eut(16)
+ .addTo(oreWasherRecipes);
+ return true;
+ }
+
+ /**
+ * IC2-Compressor Recipe. Overloads old Recipes automatically
+ */
+ @Deprecated
+ public static boolean addCompressionRecipe(ItemStack aInput, ItemStack aOutput) {
+ return addCompressionRecipe(aInput, aOutput, 300, 2);
+ }
+
+ /**
+ * IC2-Compressor Recipe. Overloads old Recipes automatically
+ */
+ @Deprecated
+ public static boolean addCompressionRecipe(ItemStack aInput, ItemStack aOutput, int duration, int EUPerTick) {
+ aOutput = GT_OreDictUnificator.get(true, aOutput);
+ if (aInput == null || aOutput == null || GT_Utility.areStacksEqual(aInput, aOutput, true)) return false;
+ RA.addCompressorRecipe(aInput, aOutput, duration, EUPerTick);
+ return true;
+ }
+
+ /**
+ * @param aValue Scrap = 5000, Scrapbox = 45000, Diamond Dust 125000
+ */
+ public static boolean addIC2MatterAmplifier(ItemStack aAmplifier, int aValue) {
+ if (aAmplifier == null || aValue <= 0) return false;
+ try {
+ NBTTagCompound tNBT = new NBTTagCompound();
+ tNBT.setInteger("amplification", aValue);
+ GT_Utility
+ .callMethod(ic2.api.recipe.Recipes.matterAmplifier, "addRecipe", false, false, false, aAmplifier, tNBT);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return true;
+ }
+
+ /**
+ * Rolling Machine Crafting Recipe
+ */
+ public static boolean addRollingMachineRecipe(ItemStack aResult, Object[] aRecipe) {
+ aResult = GT_OreDictUnificator.get(true, aResult);
+ if (aResult == null || aRecipe == null || aResult.stackSize <= 0) return false;
+ try {
+ mods.railcraft.api.crafting.RailcraftCraftingManager.rollingMachine.getRecipeList()
+ .add(new ShapedOreRecipe(GT_Utility.copyOrNull(aResult), aRecipe));
+ } catch (Throwable e) {
+ return addCraftingRecipe(GT_Utility.copyOrNull(aResult), aRecipe);
+ }
+ return true;
+ }
+
+ public static void stopBufferingCraftingRecipes() {
+ sBufferCraftingRecipes = false;
+
+ bulkRemoveRecipeByOutput(delayedRemovalByOutput);
+ bulkRemoveByRecipe(delayedRemovalByRecipe);
+ sBufferRecipeList.forEach(GameRegistry::addRecipe);
+
+ delayedRemovalByOutput.clear();
+ delayedRemovalByRecipe.clear();
+ sBufferRecipeList.clear();
+ }
+
+ /**
+ * Shapeless Crafting Recipes. Deletes conflicting Recipes too.
+ */
+ public static boolean addCraftingRecipe(ItemStack aResult, Enchantment[] aEnchantmentsAdded,
+ int[] aEnchantmentLevelsAdded, Object[] aRecipe) {
+ return addCraftingRecipe(
+ aResult,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ aRecipe);
+ }
+
+ /**
+ * Regular Crafting Recipes. Deletes conflicting Recipes too.
+ * <p/>
+ * You can insert instances of IItemContainer into the Recipe Input Array directly without having to call "get(1)"
+ * on them.
+ * <p/>
+ * Enums are automatically getting their "name()"-Method called in order to deliver an OreDict String.
+ * <p/>
+ * Lowercase Letters are reserved for Tools. They are as follows:
+ * <p/>
+ * 'b' ToolDictNames.craftingToolBlade 'c' ToolDictNames.craftingToolCrowbar, 'd'
+ * ToolDictNames.craftingToolScrewdriver, 'f' ToolDictNames.craftingToolFile, 'h'
+ * ToolDictNames.craftingToolHardHammer, 'i' ToolDictNames.craftingToolSolderingIron, 'j'
+ * ToolDictNames.craftingToolSolderingMetal, 'k' ToolDictNames.craftingToolKnive 'm'
+ * ToolDictNames.craftingToolMortar, 'p' ToolDictNames.craftingToolDrawplate, 'r'
+ * ToolDictNames.craftingToolSoftHammer, 's' ToolDictNames.craftingToolSaw, 'w' ToolDictNames.craftingToolWrench,
+ * 'x' ToolDictNames.craftingToolWireCutter,
+ */
+ public static boolean addCraftingRecipe(ItemStack aResult, Object[] aRecipe) {
+ return addCraftingRecipe(aResult, 0, aRecipe);
+ }
+
+ /**
+ * Regular Crafting Recipes. Deletes conflicting Recipes too.
+ * <p/>
+ * You can insert instances of IItemContainer into the Recipe Input Array directly without having to call "get(1)"
+ * on them.
+ * <p/>
+ * Enums are automatically getting their "name()"-Method called in order to deliver an OreDict String.
+ * <p/>
+ * Lowercase Letters are reserved for Tools. They are as follows:
+ * <p/>
+ * 'b' ToolDictNames.craftingToolBlade 'c' ToolDictNames.craftingToolCrowbar, 'd'
+ * ToolDictNames.craftingToolScrewdriver, 'f' ToolDictNames.craftingToolFile, 'h'
+ * ToolDictNames.craftingToolHardHammer, 'i' ToolDictNames.craftingToolSolderingIron, 'j'
+ * ToolDictNames.craftingToolSolderingMetal, 'k' ToolDictNames.craftingToolKnive 'm'
+ * ToolDictNames.craftingToolMortar, 'p' ToolDictNames.craftingToolDrawplate, 'r'
+ * ToolDictNames.craftingToolSoftHammer, 's' ToolDictNames.craftingToolSaw, 'w' ToolDictNames.craftingToolWrench,
+ * 'x' ToolDictNames.craftingToolWireCutter,
+ */
+ public static boolean addCraftingRecipe(ItemStack aResult, long aBitMask, Object[] aRecipe) {
+ return addCraftingRecipe(
+ aResult,
+ new Enchantment[0],
+ new int[0],
+ (aBitMask & RecipeBits.MIRRORED) != 0,
+ (aBitMask & RecipeBits.BUFFERED) != 0,
+ (aBitMask & RecipeBits.KEEPNBT) != 0,
+ (aBitMask & RecipeBits.DISMANTLEABLE) != 0,
+ (aBitMask & RecipeBits.NOT_REMOVABLE) == 0,
+ (aBitMask & RecipeBits.REVERSIBLE) != 0,
+ (aBitMask & RecipeBits.DELETE_ALL_OTHER_RECIPES) != 0,
+ (aBitMask & RecipeBits.DELETE_ALL_OTHER_RECIPES_IF_SAME_NBT) != 0,
+ (aBitMask & RecipeBits.DELETE_ALL_OTHER_SHAPED_RECIPES) != 0,
+ (aBitMask & RecipeBits.DELETE_ALL_OTHER_NATIVE_RECIPES) != 0,
+ (aBitMask & RecipeBits.DO_NOT_CHECK_FOR_COLLISIONS) == 0,
+ (aBitMask & RecipeBits.ONLY_ADD_IF_THERE_IS_ANOTHER_RECIPE_FOR_IT) != 0,
+ (aBitMask & RecipeBits.ONLY_ADD_IF_RESULT_IS_NOT_NULL) != 0,
+ aRecipe);
+ }
+
+ /**
+ * Internal realisation of the Crafting Recipe adding Process.
+ */
+ private static boolean addCraftingRecipe(ItemStack aResult, Enchantment[] aEnchantmentsAdded,
+ int[] aEnchantmentLevelsAdded, boolean aMirrored, boolean aBuffered, boolean aKeepNBT, boolean aDismantleable,
+ boolean aRemovable, boolean aReversible, boolean aRemoveAllOthersWithSameOutput,
+ boolean aRemoveAllOthersWithSameOutputIfTheyHaveSameNBT, boolean aRemoveAllOtherShapedsWithSameOutput,
+ boolean aRemoveAllOtherNativeRecipes, boolean aCheckForCollisions,
+ boolean aOnlyAddIfThereIsAnyRecipeOutputtingThis, boolean aOnlyAddIfResultIsNotNull, Object[] aRecipe) {
+
+ aResult = GT_OreDictUnificator.get(true, aResult);
+ if (aOnlyAddIfResultIsNotNull && aResult == null) return false;
+ if (aResult != null && Items.feather.getDamage(aResult) == W) Items.feather.setDamage(aResult, 0);
+ if (aRecipe == null || aRecipe.length == 0) return false;
+
+ // The renamed variable clarifies what's happening
+ // noinspection UnnecessaryLocalVariable
+ boolean tDoWeCareIfThereWasARecipe = aOnlyAddIfThereIsAnyRecipeOutputtingThis;
+ boolean tThereWasARecipe = false;
+
+ for (byte i = 0; i < aRecipe.length; i++) {
+ if (aRecipe[i] instanceof IItemContainer) aRecipe[i] = ((IItemContainer) aRecipe[i]).get(1);
+ else if (aRecipe[i] instanceof Enum) aRecipe[i] = ((Enum<?>) aRecipe[i]).name();
+ else if (!(aRecipe[i] == null || aRecipe[i] instanceof ItemStack
+ || aRecipe[i] instanceof ItemData
+ || aRecipe[i] instanceof String
+ || aRecipe[i] instanceof Character)) aRecipe[i] = aRecipe[i].toString();
+ }
+
+ try {
+ StringBuilder shape = new StringBuilder(E);
+ int idx = 0;
+ if (aRecipe[idx] instanceof Boolean) {
+ throw new IllegalArgumentException();
+ }
+
+ ArrayList<Object> tRecipeList = new ArrayList<>(Arrays.asList(aRecipe));
+
+ while (aRecipe[idx] instanceof String) {
+ StringBuilder s = new StringBuilder((String) aRecipe[idx++]);
+ shape.append(s);
+ while (s.length() < 3) s.append(" ");
+ if (s.length() > 3) throw new IllegalArgumentException();
+
+ for (char c : s.toString()
+ .toCharArray()) {
+ switch (c) {
+ case 'b' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolBlade.name());
+ }
+ case 'c' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolCrowbar.name());
+ }
+ case 'd' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolScrewdriver.name());
+ }
+ case 'f' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolFile.name());
+ }
+ case 'h' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolHardHammer.name());
+ }
+ case 'i' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolSolderingIron.name());
+ }
+ case 'j' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolSolderingMetal.name());
+ }
+ case 'k' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolKnife.name());
+ }
+ case 'm' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolMortar.name());
+ }
+ case 'p' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolDrawplate.name());
+ }
+ case 'r' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolSoftHammer.name());
+ }
+ case 's' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolSaw.name());
+ }
+ case 'w' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolWrench.name());
+ }
+ case 'x' -> {
+ tRecipeList.add(c);
+ tRecipeList.add(ToolDictNames.craftingToolWireCutter.name());
+ }
+ }
+ }
+ }
+
+ aRecipe = tRecipeList.toArray();
+
+ if (aRecipe[idx] instanceof Boolean) {
+ idx++;
+ }
+ Map<Character, ItemStack> tItemStackMap = new HashMap<>();
+ Map<Character, ItemData> tItemDataMap = new HashMap<>();
+ tItemStackMap.put(' ', null);
+
+ boolean tRemoveRecipe = true;
+
+ for (; idx < aRecipe.length; idx += 2) {
+ if (aRecipe[idx] == null || aRecipe[idx + 1] == null) {
+ if (D1) {
+ GT_Log.err.println(
+ "WARNING: Missing Item for shaped Recipe: "
+ + (aResult == null ? "null" : aResult.getDisplayName()));
+ for (Object tContent : aRecipe) GT_Log.err.println(tContent);
+ }
+ return false;
+ }
+ Character chr = (Character) aRecipe[idx];
+ Object in = aRecipe[idx + 1];
+ if (in instanceof ItemStack is) {
+ tItemStackMap.put(chr, GT_Utility.copyOrNull(is));
+ tItemDataMap.put(chr, GT_OreDictUnificator.getItemData(is));
+ } else if (in instanceof ItemData) {
+ String tString = in.toString();
+ switch (tString) {
+ case "plankWood" -> tItemDataMap.put(chr, new ItemData(Materials.Wood, M));
+ case "stoneNetherrack" -> tItemDataMap.put(chr, new ItemData(Materials.Netherrack, M));
+ case "stoneObsidian" -> tItemDataMap.put(chr, new ItemData(Materials.Obsidian, M));
+ case "stoneEndstone" -> tItemDataMap.put(chr, new ItemData(Materials.Endstone, M));
+ default -> tItemDataMap.put(chr, (ItemData) in);
+ }
+ ItemStack tStack = GT_OreDictUnificator.getFirstOre(in, 1);
+ if (tStack == null) tRemoveRecipe = false;
+ else tItemStackMap.put(chr, tStack);
+ in = aRecipe[idx + 1] = in.toString();
+ } else if (in instanceof String) {
+ if (in.equals(OreDictNames.craftingChest.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Wood, M * 8));
+ else if (in.equals(OreDictNames.craftingBook.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Paper, M * 3));
+ else if (in.equals(OreDictNames.craftingPiston.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Stone, M * 4, Materials.Wood, M * 3));
+ else if (in.equals(OreDictNames.craftingFurnace.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Stone, M * 8));
+ else if (in.equals(OreDictNames.craftingIndustrialDiamond.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Diamond, M));
+ else if (in.equals(OreDictNames.craftingAnvil.toString()))
+ tItemDataMap.put(chr, new ItemData(Materials.Iron, M * 10));
+ ItemStack tStack = GT_OreDictUnificator.getFirstOre(in, 1);
+ if (tStack == null) tRemoveRecipe = false;
+ else tItemStackMap.put(chr, tStack);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ if (aReversible && aResult != null) {
+ ItemData[] tData = new ItemData[9];
+ int x = -1;
+ for (char chr : shape.toString()
+ .toCharArray()) tData[++x] = tItemDataMap.get(chr);
+ if (GT_Utility.arrayContainsNonNull(tData))
+ GT_OreDictUnificator.addItemData(aResult, new ItemData(tData));
+ }
+
+ if (aCheckForCollisions && tRemoveRecipe) {
+ ItemStack[] tRecipe = new ItemStack[9];
+ int x = -1;
+ for (char chr : shape.toString()
+ .toCharArray()) {
+ tRecipe[++x] = tItemStackMap.get(chr);
+ if (tRecipe[x] != null && Items.feather.getDamage(tRecipe[x]) == W)
+ Items.feather.setDamage(tRecipe[x], 0);
+ }
+ if (tDoWeCareIfThereWasARecipe || !aBuffered) tThereWasARecipe = removeRecipe(tRecipe) != null;
+ else removeRecipeDelayed(tRecipe);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ }
+
+ if (aResult == null || aResult.stackSize <= 0) return false;
+
+ if (aRemoveAllOthersWithSameOutput || aRemoveAllOthersWithSameOutputIfTheyHaveSameNBT
+ || aRemoveAllOtherShapedsWithSameOutput
+ || aRemoveAllOtherNativeRecipes) {
+ if (tDoWeCareIfThereWasARecipe || !aBuffered) tThereWasARecipe = removeRecipeByOutput(
+ aResult,
+ !aRemoveAllOthersWithSameOutputIfTheyHaveSameNBT,
+ aRemoveAllOtherShapedsWithSameOutput,
+ aRemoveAllOtherNativeRecipes) || tThereWasARecipe;
+ else removeRecipeByOutputDelayed(aResult);
+ }
+
+ if (aOnlyAddIfThereIsAnyRecipeOutputtingThis && !tDoWeCareIfThereWasARecipe && !tThereWasARecipe) {
+ ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ int tList_sS = tList.size();
+ for (int i = 0; i < tList_sS && !tThereWasARecipe; i++) {
+ IRecipe tRecipe = tList.get(i);
+ if (sSpecialRecipeClasses.contains(
+ tRecipe.getClass()
+ .getName()))
+ continue;
+ if (GT_Utility.areStacksEqual(GT_OreDictUnificator.get(tRecipe.getRecipeOutput()), aResult, true)) {
+ tList.remove(i--);
+ tList_sS = tList.size();
+ tThereWasARecipe = true;
+ }
+ }
+ }
+
+ if (Items.feather.getDamage(aResult) == W || Items.feather.getDamage(aResult) < 0)
+ Items.feather.setDamage(aResult, 0);
+
+ GT_Utility.updateItemStack(aResult);
+
+ if (tThereWasARecipe || !aOnlyAddIfThereIsAnyRecipeOutputtingThis) {
+ if (sBufferCraftingRecipes && aBuffered) sBufferRecipeList.add(
+ new GT_Shaped_Recipe(
+ GT_Utility.copyOrNull(aResult),
+ aDismantleable,
+ aRemovable,
+ aKeepNBT,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ aRecipe).setMirrored(aMirrored));
+ else GameRegistry.addRecipe(
+ new GT_Shaped_Recipe(
+ GT_Utility.copyOrNull(aResult),
+ aDismantleable,
+ aRemovable,
+ aKeepNBT,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ aRecipe).setMirrored(aMirrored));
+ }
+ return true;
+ }
+
+ /**
+ * Shapeless Crafting Recipes. Deletes conflicting Recipes too.
+ */
+ public static boolean addShapelessEnchantingRecipe(ItemStack aResult, Enchantment[] aEnchantmentsAdded,
+ int[] aEnchantmentLevelsAdded, Object[] aRecipe) {
+ return addShapelessCraftingRecipe(
+ aResult,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ true,
+ false,
+ false,
+ false,
+ aRecipe);
+ }
+
+ /**
+ * Shapeless Crafting Recipes. Deletes conflicting Recipes too.
+ */
+ public static boolean addShapelessCraftingRecipe(ItemStack aResult, Object[] aRecipe) {
+ return addShapelessCraftingRecipe(
+ aResult,
+ RecipeBits.DO_NOT_CHECK_FOR_COLLISIONS | RecipeBits.BUFFERED,
+ aRecipe);
+ }
+
+ /**
+ * Shapeless Crafting Recipes. Deletes conflicting Recipes too.
+ */
+ public static boolean addShapelessCraftingRecipe(ItemStack aResult, long aBitMask, Object[] aRecipe) {
+ return addShapelessCraftingRecipe(
+ aResult,
+ new Enchantment[0],
+ new int[0],
+ (aBitMask & RecipeBits.BUFFERED) != 0,
+ (aBitMask & RecipeBits.KEEPNBT) != 0,
+ (aBitMask & RecipeBits.DISMANTLEABLE) != 0,
+ (aBitMask & RecipeBits.NOT_REMOVABLE) == 0,
+ aRecipe);
+ }
+
+ /**
+ * Shapeless Crafting Recipes. Deletes conflicting Recipes too.
+ */
+ private static boolean addShapelessCraftingRecipe(ItemStack aResult, Enchantment[] aEnchantmentsAdded,
+ int[] aEnchantmentLevelsAdded, boolean aBuffered, boolean aKeepNBT, boolean aDismantleable, boolean aRemovable,
+ Object[] aRecipe) {
+ aResult = GT_OreDictUnificator.get(true, aResult);
+ if (aRecipe == null || aRecipe.length == 0) return false;
+ for (byte i = 0; i < aRecipe.length; i++) {
+ if (aRecipe[i] instanceof IItemContainer) aRecipe[i] = ((IItemContainer) aRecipe[i]).get(1);
+ else if (aRecipe[i] instanceof Enum) aRecipe[i] = ((Enum<?>) aRecipe[i]).name();
+ else if (!(aRecipe[i] == null || aRecipe[i] instanceof ItemStack
+ || aRecipe[i] instanceof String
+ || aRecipe[i] instanceof Character)) aRecipe[i] = aRecipe[i].toString();
+ }
+ try {
+ ItemStack[] tRecipe = new ItemStack[9];
+ int i = 0;
+ for (Object tObject : aRecipe) {
+ if (tObject == null) {
+ if (D1) GT_Log.err.println(
+ "WARNING: Missing Item for shapeless Recipe: "
+ + (aResult == null ? "null" : aResult.getDisplayName()));
+ for (Object tContent : aRecipe) GT_Log.err.println(tContent);
+ return false;
+ }
+ if (tObject instanceof ItemStack) {
+ tRecipe[i] = (ItemStack) tObject;
+ } else if (tObject instanceof String) {
+ tRecipe[i] = GT_OreDictUnificator.getFirstOre(tObject, 1);
+ if (tRecipe[i] == null) break;
+ }
+ i++;
+ }
+ if (sBufferCraftingRecipes && aBuffered) removeRecipeDelayed(tRecipe);
+ else removeRecipe(tRecipe);
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ }
+
+ if (aResult == null || aResult.stackSize <= 0) return false;
+
+ if (Items.feather.getDamage(aResult) == W || Items.feather.getDamage(aResult) < 0)
+ Items.feather.setDamage(aResult, 0);
+
+ GT_Utility.updateItemStack(aResult);
+
+ if (sBufferCraftingRecipes && aBuffered) sBufferRecipeList.add(
+ new GT_Shapeless_Recipe(
+ GT_Utility.copyOrNull(aResult),
+ aDismantleable,
+ aRemovable,
+ aKeepNBT,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ aRecipe));
+ else GameRegistry.addRecipe(
+ new GT_Shapeless_Recipe(
+ GT_Utility.copyOrNull(aResult),
+ aDismantleable,
+ aRemovable,
+ aKeepNBT,
+ aEnchantmentsAdded,
+ aEnchantmentLevelsAdded,
+ aRecipe));
+ return true;
+ }
+
+ /**
+ * Removes a Smelting Recipe
+ */
+ public static boolean removeFurnaceSmelting(ItemStack aInput) {
+ if (aInput != null) {
+ for (ItemStack tInput : FurnaceRecipes.smelting()
+ .getSmeltingList()
+ .keySet()) {
+ if (GT_Utility.isStackValid(tInput) && GT_Utility.areStacksEqual(aInput, tInput, true)) {
+ FurnaceRecipes.smelting()
+ .getSmeltingList()
+ .remove(tInput);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all matching Smelting Recipes by output
+ */
+ public static boolean removeFurnaceSmeltingByOutput(ItemStack aOutput) {
+ if (aOutput != null) {
+ return FurnaceRecipes.smelting()
+ .getSmeltingList()
+ .values()
+ .removeIf(
+ tOutput -> GT_Utility.isStackValid(tOutput) && GT_Utility.areStacksEqual(aOutput, tOutput, true));
+ }
+ return false;
+ }
+
+ /**
+ * Removes a Crafting Recipe and gives you the former output of it.
+ *
+ * @param aRecipe The content of the Crafting Grid as ItemStackArray with length 9
+ * @return the output of the old Recipe or null if there was nothing.
+ */
+ public static ItemStack removeRecipe(ItemStack... aRecipe) {
+ if (aRecipe == null) return null;
+ if (Arrays.stream(aRecipe)
+ .noneMatch(Objects::nonNull)) return null;
+
+ ItemStack rReturn = null;
+ InventoryCrafting aCrafting = new InventoryCrafting(new Container() {
+
+ @Override
+ public boolean canInteractWith(EntityPlayer player) {
+ return false;
+ }
+ }, 3, 3);
+ for (int i = 0; i < aRecipe.length && i < 9; i++) aCrafting.setInventorySlotContents(i, aRecipe[i]);
+ ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ int tList_sS = tList.size();
+ try {
+ for (int i = 0; i < tList_sS; i++) {
+ for (; i < tList_sS; i++) {
+ if ((!(tList.get(i) instanceof IGT_CraftingRecipe)
+ || ((IGT_CraftingRecipe) tList.get(i)).isRemovable()) && tList.get(i)
+ .matches(aCrafting, DW)) {
+ rReturn = tList.get(i)
+ .getCraftingResult(aCrafting);
+ if (rReturn != null) tList.remove(i--);
+ tList_sS = tList.size();
+ }
+ }
+ }
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ }
+ return rReturn;
+ }
+
+ public static void removeRecipeDelayed(ItemStack... aRecipe) {
+ if (!sBufferCraftingRecipes) {
+ removeRecipe(aRecipe);
+ return;
+ }
+
+ if (aRecipe == null) return;
+ if (Arrays.stream(aRecipe)
+ .noneMatch(Objects::nonNull)) return;
+
+ InventoryCrafting aCrafting = new InventoryCrafting(new Container() {
+
+ @Override
+ public boolean canInteractWith(EntityPlayer player) {
+ return false;
+ }
+ }, 3, 3);
+ for (int i = 0; i < aRecipe.length && i < 9; i++) aCrafting.setInventorySlotContents(i, aRecipe[i]);
+ delayedRemovalByRecipe.add(aCrafting);
+ }
+
+ public static void bulkRemoveByRecipe(List<InventoryCrafting> toRemove) {
+ ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ GT_FML_LOGGER.info("BulkRemoveByRecipe: tList: " + tList.size() + " toRemove: " + toRemove.size());
+
+ Set<IRecipe> tListToRemove = tList.parallelStream()
+ .filter(tRecipe -> {
+ if ((tRecipe instanceof IGT_CraftingRecipe) && !((IGT_CraftingRecipe) tRecipe).isRemovable())
+ return false;
+ return toRemove.stream()
+ .anyMatch(aCrafting -> tRecipe.matches(aCrafting, DW));
+ })
+ .collect(Collectors.toSet());
+
+ tList.removeIf(tListToRemove::contains);
+ }
+
+ public static boolean removeRecipeByOutputDelayed(ItemStack aOutput) {
+ if (sBufferCraftingRecipes) return delayedRemovalByOutput.add(aOutput);
+ else return removeRecipeByOutput(aOutput);
+ }
+
+ public static boolean removeRecipeByOutputDelayed(ItemStack aOutput, boolean aIgnoreNBT,
+ boolean aNotRemoveShapelessRecipes, boolean aOnlyRemoveNativeHandlers) {
+ if (sBufferCraftingRecipes && (aIgnoreNBT && !aNotRemoveShapelessRecipes && !aOnlyRemoveNativeHandlers))
+ // Too lazy to handle deferred versions of the parameters that aren't used very often
+ return delayedRemovalByOutput.add(aOutput);
+ else return removeRecipeByOutput(aOutput, aIgnoreNBT, aNotRemoveShapelessRecipes, aOnlyRemoveNativeHandlers);
+ }
+
+ public static boolean removeRecipeByOutput(ItemStack aOutput) {
+ return removeRecipeByOutput(aOutput, true, false, false);
+ }
+
+ /**
+ * Removes a Crafting Recipe.
+ *
+ * @param aOutput The output of the Recipe.
+ * @return if it has removed at least one Recipe.
+ */
+ public static boolean removeRecipeByOutput(ItemStack aOutput, boolean aIgnoreNBT,
+ boolean aNotRemoveShapelessRecipes, boolean aOnlyRemoveNativeHandlers) {
+ if (aOutput == null) return false;
+ boolean rReturn = false;
+ final ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ aOutput = GT_OreDictUnificator.get(aOutput);
+ int tList_sS = tList.size();
+ for (int i = 0; i < tList_sS; i++) {
+ final IRecipe tRecipe = tList.get(i);
+ if (aNotRemoveShapelessRecipes
+ && (tRecipe instanceof ShapelessRecipes || tRecipe instanceof ShapelessOreRecipe)) continue;
+ if (aOnlyRemoveNativeHandlers) {
+ if (!sNativeRecipeClasses.contains(
+ tRecipe.getClass()
+ .getName()))
+ continue;
+ } else {
+ if (sSpecialRecipeClasses.contains(
+ tRecipe.getClass()
+ .getName()))
+ continue;
+ }
+ ItemStack tStack = tRecipe.getRecipeOutput();
+ if ((!(tRecipe instanceof IGT_CraftingRecipe) || ((IGT_CraftingRecipe) tRecipe).isRemovable())
+ && GT_Utility.areStacksEqual(GT_OreDictUnificator.get(tStack), aOutput, aIgnoreNBT)) {
+ tList.remove(i--);
+ tList_sS = tList.size();
+ rReturn = true;
+ }
+ }
+ return rReturn;
+ }
+
+ public static boolean bulkRemoveRecipeByOutput(List<ItemStack> toRemove) {
+ ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+
+ Set<ItemStack> setToRemove = toRemove.parallelStream()
+ .map(GT_OreDictUnificator::get_nocopy)
+ .collect(Collectors.toSet());
+
+ GT_FML_LOGGER.info("BulkRemoveRecipeByOutput: tList: " + tList.size() + " setToRemove: " + setToRemove.size());
+
+ Set<IRecipe> tListToRemove = tList.parallelStream()
+ .filter(tRecipe -> {
+ if ((tRecipe instanceof IGT_CraftingRecipe) && !((IGT_CraftingRecipe) tRecipe).isRemovable())
+ return false;
+ if (sSpecialRecipeClasses.contains(
+ tRecipe.getClass()
+ .getName()))
+ return false;
+ final ItemStack tStack = GT_OreDictUnificator.get_nocopy(tRecipe.getRecipeOutput());
+ return setToRemove.stream()
+ .anyMatch(aOutput -> GT_Utility.areStacksEqual(tStack, aOutput, true));
+ })
+ .collect(Collectors.toSet());
+
+ tList.removeIf(tListToRemove::contains);
+ return true;
+ }
+
+ /**
+ * Checks all Crafting Handlers for Recipe Output Used for the Autocrafting Table
+ */
+ public static ItemStack getAllRecipeOutput(World aWorld, ItemStack... aRecipe) {
+ if (aRecipe == null || aRecipe.length == 0) return null;
+
+ if (aWorld == null) aWorld = DW;
+
+ boolean temp = false;
+ for (ItemStack itemStack : aRecipe) {
+ if (itemStack != null) {
+ temp = true;
+ break;
+ }
+ }
+ if (!temp) return null;
+ InventoryCrafting aCrafting = new InventoryCrafting(new Container() {
+
+ @Override
+ public boolean canInteractWith(EntityPlayer player) {
+ return false;
+ }
+ }, 3, 3);
+ for (int i = 0; i < 9 && i < aRecipe.length; i++) aCrafting.setInventorySlotContents(i, aRecipe[i]);
+ List<IRecipe> tList = CraftingManager.getInstance()
+ .getRecipeList();
+ synchronized (sAllRecipeList) {
+ if (sAllRecipeList.size() != tList.size()) {
+ sAllRecipeList.clear();
+ sAllRecipeList.addAll(tList);
+ }
+ for (int i = 0, j = sAllRecipeList.size(); i < j; i++) {
+ IRecipe tRecipe = sAllRecipeList.get(i);
+ if (tRecipe.matches(aCrafting, aWorld)) {
+ if (i > 10) {
+ sAllRecipeList.remove(i);
+ sAllRecipeList.add(i - 10, tRecipe);
+ }
+ return tRecipe.getCraftingResult(aCrafting);
+ }
+ }
+ }
+
+ int tIndex = 0;
+ ItemStack tStack1 = null, tStack2 = null;
+ for (int i = 0, j = aCrafting.getSizeInventory(); i < j; i++) {
+ ItemStack tStack = aCrafting.getStackInSlot(i);
+ if (tStack != null) {
+ if (tIndex == 0) tStack1 = tStack;
+ if (tIndex == 1) tStack2 = tStack;
+ tIndex++;
+ }
+ }
+
+ if (tIndex == 2 && tStack2 != null) {
+ if (tStack1.getItem() == tStack2.getItem() && tStack1.stackSize == 1
+ && tStack2.stackSize == 1
+ && tStack1.getItem()
+ .isRepairable()) {
+ int tNewDamage = tStack1.getMaxDamage() + tStack1.getItemDamage()
+ - tStack2.getItemDamage()
+ + tStack1.getMaxDamage() / 20;
+ return new ItemStack(tStack1.getItem(), 1, Math.max(tNewDamage, 0));
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gives you a copy of the Output from a Crafting Recipe Used for Recipe Detection.
+ */
+ public static ItemStack getRecipeOutput(ItemStack... aRecipe) {
+ return getRecipeOutput(false, true, aRecipe);
+ }
+
+ public static ItemStack getRecipeOutputNoOreDict(ItemStack... aRecipe) {
+ return getRecipeOutput(false, false, aRecipe);
+ }
+
+ public static ItemStack getRecipeOutput(boolean aUncopiedStack, ItemStack... aRecipe) {
+ return getRecipeOutput(aUncopiedStack, true, aRecipe);
+ }
+
+ /**
+ * Gives you a copy of the Output from a Crafting Recipe Used for Recipe Detection.
+ */
+ public static ItemStack getRecipeOutput(boolean aUncopiedStack, boolean allowOreDict, ItemStack... aRecipe) {
+ if (aRecipe == null || Arrays.stream(aRecipe)
+ .noneMatch(Objects::nonNull)) return null;
+
+ InventoryCrafting aCrafting = new InventoryCrafting(new Container() {
+
+ @Override
+ public boolean canInteractWith(EntityPlayer player) {
+ return false;
+ }
+ }, 3, 3);
+ for (int i = 0; i < 9 && i < aRecipe.length; i++) aCrafting.setInventorySlotContents(i, aRecipe[i]);
+ ArrayList<IRecipe> tList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ boolean found = false;
+
+ for (IRecipe iRecipe : tList) {
+ found = false;
+ if (!allowOreDict && iRecipe instanceof ShapedOreRecipe) continue;
+
+ try {
+ found = iRecipe.matches(aCrafting, DW);
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ }
+ if (found) {
+ ItemStack tOutput = aUncopiedStack ? iRecipe.getRecipeOutput() : iRecipe.getCraftingResult(aCrafting);
+ if (tOutput == null || tOutput.stackSize <= 0) {
+ // Seriously, who would ever do that shit?
+ if (!GregTech_API.sPostloadFinished) throw new GT_ItsNotMyFaultException(
+ "Seems another Mod added a Crafting Recipe with null Output. Tell the Developer of said Mod to fix that.");
+ } else {
+ if (aUncopiedStack) return tOutput;
+ return GT_Utility.copyOrNull(tOutput);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gives you a list of the Outputs from a Crafting Recipe If you have multiple Mods, which add Bronze Armor for
+ * example This also removes old Recipes from the List.
+ */
+ public static List<ItemStack> getVanillyToolRecipeOutputs(ItemStack... aRecipe) {
+ if (!GregTech_API.sPostloadStarted || GregTech_API.sPostloadFinished)
+ sSingleNonBlockDamagableRecipeList.clear();
+ if (sSingleNonBlockDamagableRecipeList.isEmpty()) {
+ for (IRecipe tRecipe : CraftingManager.getInstance()
+ .getRecipeList()) {
+ ItemStack tStack = tRecipe.getRecipeOutput();
+ if (GT_Utility.isStackValid(tStack) && tStack.getMaxStackSize() == 1
+ && tStack.getMaxDamage() > 0
+ && !(tStack.getItem() instanceof ItemBlock)
+ && !(tStack.getItem() instanceof IReactorComponent)
+ && !isElectricItem(tStack)
+ && !GT_Utility.isStackInList(tStack, sNonReplaceableItems)) {
+ if (!(tRecipe instanceof ShapelessRecipes || tRecipe instanceof ShapelessOreRecipe)) {
+ if (tRecipe instanceof ShapedOreRecipe) {
+ boolean temp = true;
+ for (Object tObject : ((ShapedOreRecipe) tRecipe).getInput()) if (tObject != null) {
+ if (tObject instanceof ItemStack && (((ItemStack) tObject).getItem() == null
+ || ((ItemStack) tObject).getMaxStackSize() < 2
+ || ((ItemStack) tObject).getMaxDamage() > 0
+ || ((ItemStack) tObject).getItem() instanceof ItemBlock)) {
+ temp = false;
+ break;
+ }
+ if (tObject instanceof List && ((List<?>) tObject).isEmpty()) {
+ temp = false;
+ break;
+ }
+ }
+ if (temp) sSingleNonBlockDamagableRecipeList.add(tRecipe);
+ } else if (tRecipe instanceof ShapedRecipes) {
+ boolean temp = true;
+ for (ItemStack tObject : ((ShapedRecipes) tRecipe).recipeItems) {
+ if (tObject != null && (tObject.getItem() == null || tObject.getMaxStackSize() < 2
+ || tObject.getMaxDamage() > 0
+ || tObject.getItem() instanceof ItemBlock)) {
+ temp = false;
+ break;
+ }
+ }
+ if (temp) sSingleNonBlockDamagableRecipeList.add(tRecipe);
+ } else {
+ sSingleNonBlockDamagableRecipeList.add(tRecipe);
+ }
+ }
+ }
+ }
+ GT_Log.out.println(
+ "GT_Mod: Created a List of Tool Recipes containing " + sSingleNonBlockDamagableRecipeList.size()
+ + " Recipes for recycling."
+ + (sSingleNonBlockDamagableRecipeList.size() > 1024
+ ? " Scanning all these Recipes is the reason for the startup Lag you receive right now."
+ : E));
+ }
+ List<ItemStack> rList = getRecipeOutputs(sSingleNonBlockDamagableRecipeList, true, aRecipe);
+ if (!GregTech_API.sPostloadStarted || GregTech_API.sPostloadFinished)
+ sSingleNonBlockDamagableRecipeList.clear();
+ return rList;
+ }
+
+ /**
+ * Gives you a list of the Outputs from a Crafting Recipe If you have multiple Mods, which add Bronze Armor for
+ * example
+ */
+ public static List<ItemStack> getRecipeOutputs(ItemStack... aRecipe) {
+ return getRecipeOutputs(
+ CraftingManager.getInstance()
+ .getRecipeList(),
+ false,
+ aRecipe);
+ }
+
+ private static List<IRecipe> bufferedRecipes = null;
+
+ /**
+ * Gives you a list of the Outputs from a Crafting Recipe If you have multiple Mods, which add Bronze Armor for
+ * example Buffers a List which only has armor-alike crafting in it
+ */
+ public static List<ItemStack> getRecipeOutputsBuffered(ItemStack... aRecipe) {
+
+ if (bufferedRecipes == null) bufferedRecipes = CraftingManager.getInstance()
+ .getRecipeList()
+ .stream()
+ .filter(
+ tRecipe -> !(tRecipe instanceof ShapelessRecipes) && !(tRecipe instanceof ShapelessOreRecipe)
+ && !(tRecipe instanceof IGT_CraftingRecipe))
+ .filter(tRecipe -> {
+ try {
+ ItemStack tOutput = tRecipe.getRecipeOutput();
+ if (tOutput.stackSize == 1 && tOutput.getMaxDamage() > 0 && tOutput.getMaxStackSize() == 1) {
+ return true;
+ }
+ } catch (Exception ignored) {}
+ return false;
+ })
+ .collect(Collectors.toList());
+ return getRecipeOutputs(bufferedRecipes, false, aRecipe);
+ }
+
+ /**
+ * Gives you a list of the Outputs from a Crafting Recipe If you have multiple Mods, which add Bronze Armor for
+ * example
+ */
+ public static List<ItemStack> getRecipeOutputs(List<IRecipe> aList, boolean aDeleteFromList, ItemStack... aRecipe) {
+ List<ItemStack> rList = new ArrayList<>();
+ if (aRecipe == null || Arrays.stream(aRecipe)
+ .noneMatch(Objects::nonNull)) return rList;
+ InventoryCrafting aCrafting = new InventoryCrafting(new Container() {
+
+ @Override
+ public boolean canInteractWith(EntityPlayer player) {
+ return false;
+ }
+ }, 3, 3);
+ for (int i = 0; i < 9 && i < aRecipe.length; i++) aCrafting.setInventorySlotContents(i, aRecipe[i]);
+ if (!aDeleteFromList) {
+ HashSet<ItemStack> stacks = new HashSet<>();
+ aList.stream()
+ .filter(tRecipe -> {
+ if (tRecipe instanceof ShapelessRecipes || tRecipe instanceof ShapelessOreRecipe
+ || tRecipe instanceof IGT_CraftingRecipe) return false;
+ try {
+ return tRecipe.matches(aCrafting, DW);
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ return false;
+ }
+ })
+ .forEach(tRecipe -> stacks.add(tRecipe.getCraftingResult(aCrafting)));
+ rList = stacks.stream()
+ .filter(
+ tOutput -> tOutput.stackSize == 1 && tOutput.getMaxDamage() > 0 && tOutput.getMaxStackSize() == 1)
+ .collect(Collectors.toList());
+ } else for (Iterator<IRecipe> iterator = aList.iterator(); iterator.hasNext();) {
+ IRecipe tRecipe = iterator.next();
+ boolean matched = false;
+
+ try {
+ matched = tRecipe.matches(aCrafting, DW);
+ } catch (Throwable e) {
+ e.printStackTrace(GT_Log.err);
+ }
+ if (matched) {
+ ItemStack tOutput = tRecipe.getCraftingResult(aCrafting);
+
+ if (tOutput == null || tOutput.stackSize <= 0) {
+ // Seriously, who would ever do that shit?
+ if (!GregTech_API.sPostloadFinished) throw new GT_ItsNotMyFaultException(
+ "Seems another Mod added a Crafting Recipe with null Output. Tell the Developer of said Mod to fix that.");
+ continue;
+ }
+ if (tOutput.stackSize != 1) continue;
+ if (tOutput.getMaxDamage() <= 0) continue;
+ if (tOutput.getMaxStackSize() != 1) continue;
+ if (tRecipe instanceof ShapelessRecipes) continue;
+ if (tRecipe instanceof ShapelessOreRecipe) continue;
+ if (tRecipe instanceof IGT_CraftingRecipe) continue;
+ rList.add(GT_Utility.copyOrNull(tOutput));
+ iterator.remove();
+ }
+ }
+ return rList;
+ }
+
+ /**
+ * Used in my own Macerator. Decreases StackSize of the Input if wanted.
+ */
+ @Deprecated
+ public static ItemStack getMaceratorOutput(ItemStack aInput, boolean aRemoveInput, ItemStack aOutputSlot) {
+ return GT_Utility.copyOrNull(
+ getMachineOutput(aInput, getMaceratorRecipeList(), aRemoveInput, new NBTTagCompound(), aOutputSlot)[0]);
+ }
+
+ /**
+ * Used in my own Extractor. Decreases StackSize of the Input if wanted.
+ */
+ @Deprecated
+ public static ItemStack getExtractorOutput(ItemStack aInput, boolean aRemoveInput, ItemStack aOutputSlot) {
+ return GT_Utility.copyOrNull(
+ getMachineOutput(aInput, getExtractorRecipeList(), aRemoveInput, new NBTTagCompound(), aOutputSlot)[0]);
+ }
+
+ /**
+ * Used in my own Compressor. Decreases StackSize of the Input if wanted.
+ */
+ @Deprecated
+ public static ItemStack getCompressorOutput(ItemStack aInput, boolean aRemoveInput, ItemStack aOutputSlot) {
+ return GT_Utility.copyOrNull(
+ getMachineOutput(aInput, getCompressorRecipeList(), aRemoveInput, new NBTTagCompound(), aOutputSlot)[0]);
+ }
+
+ /**
+ * Used in my own Furnace.
+ */
+ public static ItemStack getSmeltingOutput(ItemStack aInput, boolean aRemoveInput, ItemStack aOutputSlot) {
+ if (aInput == null || aInput.stackSize < 1) return null;
+ ItemStack rStack = GT_OreDictUnificator.get(
+ FurnaceRecipes.smelting()
+ .getSmeltingResult(aInput));
+
+ if (rStack != null && (aOutputSlot == null || (GT_Utility.areStacksEqual(rStack, aOutputSlot)
+ && rStack.stackSize + aOutputSlot.stackSize <= aOutputSlot.getMaxStackSize()))) {
+ if (aRemoveInput) aInput.stackSize--;
+ return rStack;
+ }
+ return null;
+ }
+
+ /**
+ * Used in my own Machines. Decreases StackSize of the Input if wanted.
+ * <p/>
+ * Checks also if there is enough Space in the Output Slots.
+ */
+ public static ItemStack[] getMachineOutput(ItemStack aInput, Map<IRecipeInput, RecipeOutput> aRecipeList,
+ boolean aRemoveInput, NBTTagCompound rRecipeMetaData, ItemStack... aOutputSlots) {
+ if (aOutputSlots == null || aOutputSlots.length == 0) return new ItemStack[0];
+ if (aInput == null) return new ItemStack[aOutputSlots.length];
+ try {
+ for (Entry<IRecipeInput, RecipeOutput> tEntry : aRecipeList.entrySet()) {
+ if (tEntry.getKey()
+ .matches(aInput)) {
+ if (tEntry.getKey()
+ .getAmount() <= aInput.stackSize) {
+ ItemStack[] tList = tEntry.getValue().items.toArray(new ItemStack[0]);
+ if (tList.length == 0) break;
+ ItemStack[] rList = new ItemStack[aOutputSlots.length];
+ rRecipeMetaData.setTag("return", tEntry.getValue().metadata);
+ for (byte i = 0; i < aOutputSlots.length && i < tList.length; i++) {
+ if (tList[i] != null) {
+ if (aOutputSlots[i] == null || (GT_Utility.areStacksEqual(tList[i], aOutputSlots[i])
+ && tList[i].stackSize + aOutputSlots[i].stackSize
+ <= aOutputSlots[i].getMaxStackSize())) {
+ rList[i] = GT_Utility.copyOrNull(tList[i]);
+ } else {
+ return new ItemStack[aOutputSlots.length];
+ }
+ }
+ }
+
+ if (aRemoveInput) aInput.stackSize -= tEntry.getKey()
+ .getAmount();
+ return rList;
+ }
+ break;
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return new ItemStack[aOutputSlots.length];
+ }
+
+ /**
+ * Used in my own Recycler.
+ * <p/>
+ * Only produces Scrap if aScrapChance == 0. aScrapChance is usually the random Number I give to the Function If you
+ * directly insert 0 as aScrapChance then you can check if its Recycler-Blacklisted or similar
+ */
+ @Nullable
+ public static ItemStack getRecyclerOutput(ItemStack aInput, int aScrapChance) {
+ if (aInput == null || aScrapChance != 0) return null;
+ if (recyclerWhitelist == null) {
+ generateRecyclerCache();
+ }
+
+ if (recyclerWhitelist.isEmpty()) {
+ if (searchRecyclerCache(aInput, recyclerBlacklist)) {
+ return null;
+ } else {
+ return ItemList.IC2_Scrap.get(1);
+ }
+ } else {
+ if (searchRecyclerCache(aInput, recyclerWhitelist)) {
+ return ItemList.IC2_Scrap.get(1);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static void generateRecyclerCache() {
+ recyclerWhitelist = new HashSet<>();
+ for (IRecipeInput input : ic2.api.recipe.Recipes.recyclerWhitelist) {
+ for (ItemStack stack : input.getInputs()) {
+ recyclerWhitelist.add(GT_Utility.ItemId.create(stack.getItem(), stack.getItemDamage(), null));
+ }
+ }
+ recyclerBlacklist = new HashSet<>();
+ for (IRecipeInput input : ic2.api.recipe.Recipes.recyclerBlacklist) {
+ for (ItemStack stack : input.getInputs()) {
+ recyclerBlacklist.add(GT_Utility.ItemId.create(stack.getItem(), stack.getItemDamage(), null));
+ }
+ }
+ }
+
+ private static boolean searchRecyclerCache(ItemStack stack, Set<GT_Utility.ItemId> set) {
+ if (set.contains(GT_Utility.ItemId.createWithoutNBT(stack))) {
+ return true;
+ }
+ // ic2.api.recipe.RecipeInputItemStack#matches expects item with wildcard meta to accept arbitrary meta
+ return set.contains(GT_Utility.ItemId.createAsWildcard(stack));
+ }
+
+ /**
+ * For the Scrapboxinator
+ */
+ public static ItemStack getRandomScrapboxDrop() {
+ return ic2.api.recipe.Recipes.scrapboxDrops.getDrop(ItemList.IC2_Scrapbox.get(1), false);
+ }
+
+ /**
+ * Charges an Electric Item. Only if it's a valid Electric Item of course. This forces the Usage of proper Voltages
+ * (so not the transfer limits defined by the Items) unless you ignore the Transfer Limit. If aTier is
+ * Integer.MAX_VALUE it will ignore Tier based Limitations.
+ *
+ * @return the actually used Energy.
+ */
+ public static int chargeElectricItem(ItemStack aStack, int aCharge, int aTier, boolean aIgnoreLimit,
+ boolean aSimulate) {
+ try {
+ if (isElectricItem(aStack)) {
+ int tTier = ((ic2.api.item.IElectricItem) aStack.getItem()).getTier(aStack);
+ if (tTier < 0 || tTier == aTier || aTier == Integer.MAX_VALUE) {
+ if (!aIgnoreLimit && tTier >= 0)
+ aCharge = (int) Math.min(aCharge, V[Math.max(0, Math.min(V.length - 1, tTier))]);
+ if (aCharge > 0) {
+ int rCharge = (int) Math.max(
+ 0.0,
+ ic2.api.item.ElectricItem.manager.charge(aStack, aCharge, tTier, true, aSimulate));
+ return rCharge + (rCharge * 4 > aTier ? aTier : 0);
+ }
+ }
+ }
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return 0;
+ }
+
+ /**
+ * Discharges an Electric Item. Only if it's a valid Electric Item for that of course. This forces the Usage of
+ * proper Voltages (so not the transfer limits defined by the Items) unless you ignore the Transfer Limit. If aTier
+ * is Integer.MAX_VALUE it will ignore Tier based Limitations.
+ *
+ * @return the Energy got from the Item.
+ */
+ public static int dischargeElectricItem(ItemStack aStack, int aCharge, int aTier, boolean aIgnoreLimit,
+ boolean aSimulate, boolean aIgnoreDischargability) {
+ try {
+ // if (isElectricItem(aStack) && (aIgnoreDischargability ||
+ // ((ic2.api.item.IElectricItem)aStack.getItem()).canProvideEnergy(aStack))) {
+ if (isElectricItem(aStack)) {
+ int tTier = ((ic2.api.item.IElectricItem) aStack.getItem()).getTier(aStack);
+ if (tTier < 0 || tTier == aTier || aTier == Integer.MAX_VALUE) {
+ if (!aIgnoreLimit && tTier >= 0) aCharge = (int) Math.min(
+ aCharge,
+ V[Math.max(0, Math.min(V.length - 1, tTier))] + B[Math.max(0, Math.min(V.length - 1, tTier))]);
+ if (aCharge > 0) {
+ int rCharge = (int) Math.max(
+ 0,
+ ic2.api.item.ElectricItem.manager.discharge(
+ aStack,
+ aCharge + (aCharge * 4 > aTier ? aTier : 0),
+ tTier,
+ true,
+ !aIgnoreDischargability,
+ aSimulate));
+ return rCharge - (rCharge * 4 > aTier ? aTier : 0);
+ }
+ }
+ }
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return 0;
+ }
+
+ /**
+ * Uses an Electric Item. Only if it's a valid Electric Item for that of course.
+ *
+ * @return if the action was successful
+ */
+ public static boolean canUseElectricItem(ItemStack aStack, int aCharge) {
+ try {
+ if (isElectricItem(aStack)) {
+ return ic2.api.item.ElectricItem.manager.canUse(aStack, aCharge);
+ }
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return false;
+ }
+
+ /**
+ * Uses an Electric Item. Only if it's a valid Electric Item for that of course.
+ *
+ * @return if the action was successful
+ */
+ public static boolean useElectricItem(ItemStack aStack, int aCharge, EntityPlayer aPlayer) {
+ try {
+ if (isElectricItem(aStack)) {
+ ic2.api.item.ElectricItem.manager.use(aStack, 0, aPlayer);
+ if (ic2.api.item.ElectricItem.manager.canUse(aStack, aCharge)) {
+ return ic2.api.item.ElectricItem.manager.use(aStack, aCharge, aPlayer);
+ }
+ }
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return false;
+ }
+
+ /**
+ * Uses an Item. Tries to discharge in case of Electric Items
+ */
+ public static boolean damageOrDechargeItem(ItemStack aStack, int aDamage, int aDecharge, EntityLivingBase aPlayer) {
+ if (GT_Utility.isStackInvalid(aStack) || (aStack.getMaxStackSize() <= 1 && aStack.stackSize > 1)) return false;
+ if (aPlayer instanceof EntityPlayer && ((EntityPlayer) aPlayer).capabilities.isCreativeMode) return true;
+ if (aStack.getItem() instanceof IDamagableItem) {
+ return ((IDamagableItem) aStack.getItem()).doDamageToItem(aStack, aDamage);
+ } else if (GT_ModHandler.isElectricItem(aStack)) {
+ if (canUseElectricItem(aStack, aDecharge)) {
+ if (aPlayer instanceof EntityPlayer) {
+ return GT_ModHandler.useElectricItem(aStack, aDecharge, (EntityPlayer) aPlayer);
+ }
+ return GT_ModHandler.dischargeElectricItem(aStack, aDecharge, Integer.MAX_VALUE, true, false, true)
+ >= aDecharge;
+ }
+ } else if (aStack.getItem()
+ .isDamageable()) {
+ if (aPlayer == null) {
+ aStack.setItemDamage(aStack.getItemDamage() + aDamage);
+ } else {
+ aStack.damageItem(aDamage, aPlayer);
+ }
+ if (aStack.getItemDamage() >= aStack.getMaxDamage()) {
+ aStack.setItemDamage(aStack.getMaxDamage() + 1);
+ ItemStack tStack = GT_Utility.getContainerItem(aStack, true);
+ if (tStack != null) {
+ aStack.func_150996_a(tStack.getItem());
+ aStack.setItemDamage(tStack.getItemDamage());
+ aStack.stackSize = tStack.stackSize;
+ aStack.setTagCompound(tStack.getTagCompound());
+ } else if (aStack.stackSize > 0) {
+ aStack.stackSize--;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Uses a Soldering Iron from player or external inventory
+ */
+ public static boolean useSolderingIron(ItemStack aStack, EntityLivingBase aPlayer, IInventory aExternalInventory) {
+ if (aPlayer == null || aStack == null) return false;
+ if (GT_Utility.isStackInList(aStack, GregTech_API.sSolderingToolList)) {
+ if (aPlayer instanceof EntityPlayer tPlayer) {
+ if (tPlayer.capabilities.isCreativeMode) return true;
+ if (isElectricItem(aStack) && ic2.api.item.ElectricItem.manager.getCharge(aStack) > 1000.0d) {
+ if (consumeSolderingMaterial(tPlayer)
+ || (aExternalInventory != null && consumeSolderingMaterial(aExternalInventory))) {
+ if (canUseElectricItem(aStack, 10000)) {
+ return GT_ModHandler.useElectricItem(aStack, 10000, (EntityPlayer) aPlayer);
+ }
+ GT_ModHandler.useElectricItem(
+ aStack,
+ (int) ic2.api.item.ElectricItem.manager.getCharge(aStack),
+ (EntityPlayer) aPlayer);
+ return false;
+ } else {
+ GT_Utility.sendChatToPlayer(
+ (EntityPlayer) aPlayer,
+ GT_Utility.trans("094.1", "Not enough soldering material!"));
+ }
+ }
+ } else {
+ damageOrDechargeItem(aStack, 1, 1000, aPlayer);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean useSolderingIron(ItemStack aStack, EntityLivingBase aPlayer) {
+ return useSolderingIron(aStack, aPlayer, null);
+ }
+
+ public static boolean consumeSolderingMaterial(EntityPlayer aPlayer) {
+ if (aPlayer.capabilities.isCreativeMode) return true;
+ IInventory tInventory = aPlayer.inventory;
+ if (consumeSolderingMaterial(tInventory)) {
+ if (aPlayer.inventoryContainer != null) {
+ aPlayer.inventoryContainer.detectAndSendChanges();
+ }
+ return true;
+ }
+ for (int i = 0; i < tInventory.getSizeInventory(); i++) {
+ ItemStack tStack = tInventory.getStackInSlot(i);
+ if (tStack != null && tStack.getItem() instanceof ItemToolbox) {
+ IInventory tToolboxInventory = ((ItemToolbox) tStack.getItem()).getInventory(aPlayer, tStack);
+ if (consumeSolderingMaterial(tToolboxInventory)) {
+ tInventory.markDirty();
+ if (aPlayer.inventoryContainer != null) {
+ aPlayer.inventoryContainer.detectAndSendChanges();
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Consumes soldering material from given inventory
+ */
+ public static boolean consumeSolderingMaterial(IInventory aInventory) {
+ for (int i = 0; i < aInventory.getSizeInventory(); i++) {
+ ItemStack tStack = aInventory.getStackInSlot(i);
+ if (GT_Utility.isStackInList(tStack, GregTech_API.sSolderingMetalList)) {
+ if (tStack.stackSize < 1) return false;
+ if (tStack.stackSize == 1) {
+ tStack = null;
+ } else {
+ tStack.stackSize--;
+ }
+ aInventory.setInventorySlotContents(i, tStack);
+ aInventory.markDirty();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Is this an electric Item, which can charge other Items?
+ */
+ public static boolean isChargerItem(ItemStack aStack) {
+ try {
+ if (isElectricItem(aStack)) {
+ return ((ic2.api.item.IElectricItem) aStack.getItem()).canProvideEnergy(aStack);
+ }
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return false;
+ }
+
+ /**
+ * Is this an electric Item?
+ */
+ public static boolean isElectricItem(ItemStack aStack) {
+ try {
+ return aStack != null && aStack.getItem() instanceof ic2.api.item.IElectricItem
+ && ((IElectricItem) aStack.getItem()).getTier(aStack) < Integer.MAX_VALUE;
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return false;
+ }
+
+ public static boolean isElectricItem(ItemStack aStack, byte aTier) {
+ try {
+ return aStack != null && aStack.getItem() instanceof ic2.api.item.IElectricItem
+ && ((IElectricItem) aStack.getItem()).getTier(aStack) == aTier;
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current charge and maximum charge of an ItemStack.
+ *
+ * @param aStack Any ItemStack.
+ * @return Optional.empty() if the stack is null or not an electric item, or an Optional containing a payload of an
+ * array containing [ current_charge, maximum_charge ] on success.
+ */
+ public static Optional<Long[]> getElectricItemCharge(ItemStack aStack) {
+ if (aStack == null || !isElectricItem(aStack)) {
+ return Optional.empty();
+ }
+
+ final Item item = aStack.getItem();
+
+ if (item instanceof final GT_MetaBase_Item metaBaseItem) {
+ final Long[] stats = metaBaseItem.getElectricStats(aStack);
+ if (stats != null && stats.length > 0) {
+ return Optional.of(new Long[] { metaBaseItem.getRealCharge(aStack), stats[0] });
+ }
+
+ } else if (item instanceof final IElectricItem ic2ElectricItem) {
+ return Optional.of(
+ new Long[] { (long) ic2.api.item.ElectricItem.manager.getCharge(aStack),
+ (long) ic2ElectricItem.getMaxCharge(aStack) });
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Allow item to be inserted into ic2 toolbox
+ */
+ public static void registerBoxableItemToToolBox(ItemStack aStack) {
+ if (aStack != null) {
+ try {
+ ic2.api.item.ItemWrapper.registerBoxable(aStack.getItem(), (IBoxable) sBoxableWrapper);
+ } catch (Throwable ignored) {
+ /* Do nothing */
+ }
+ sBoxableItems.add(new GT_ItemStack(aStack));
+ }
+ }
+
+ @Deprecated
+ public static void registerBoxableItemToToolBox(Item aItem) {
+ registerBoxableItemToToolBox(new ItemStack(aItem, 1, GT_Values.W));
+ }
+
+ public static int getCapsuleCellContainerCountMultipliedWithStackSize(ItemStack... aStacks) {
+ int rAmount = 0;
+ for (ItemStack tStack : aStacks)
+ if (tStack != null) rAmount += getCapsuleCellContainerCount(tStack) * tStack.stackSize;
+ return rAmount;
+ }
+
+ public static int getCapsuleCellContainerCount(ItemStack aStack) {
+ if (aStack == null) return 0;
+
+ if (GT_Utility.areStacksEqual(GT_Utility.getContainerForFilledItem(aStack, true), ItemList.Cell_Empty.get(1))) {
+ return 1;
+ }
+
+ if (GT_Utility.areStacksEqual(aStack, getIC2Item("waterCell", 1, W))) {
+ return 1;
+ }
+
+ for (OrePrefixes cellType : OrePrefixes.CELL_TYPES) {
+ if (cellType.contains(aStack)) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ public static class RecipeBits {
+
+ /**
+ * Mirrors the Recipe
+ */
+ public static long MIRRORED = B[0];
+ /**
+ * Buffers the Recipe for later addition. This makes things more efficient.
+ */
+ public static long BUFFERED = B[1];
+ /**
+ * This is a special Tag I used for crafting Coins up and down.
+ */
+ public static long KEEPNBT = B[2];
+ /**
+ * Makes the Recipe Reverse Craftable in the Disassembler.
+ */
+ public static long DISMANTLEABLE = B[3];
+ /**
+ * Prevents the Recipe from accidentally getting removed by my own Handlers.
+ */
+ public static long NOT_REMOVABLE = B[4];
+ /**
+ * Reverses the Output of the Recipe for smelting and pulverising.
+ */
+ public static long REVERSIBLE = B[5];
+ /**
+ * Removes all Recipes with the same Output Item regardless of NBT, unless another Recipe Deletion Bit is added
+ * too.
+ */
+ public static long DELETE_ALL_OTHER_RECIPES = B[6];
+ /**
+ * Removes all Recipes with the same Output Item limited to the same NBT.
+ */
+ public static long DELETE_ALL_OTHER_RECIPES_IF_SAME_NBT = B[7];
+ /**
+ * Removes all Recipes with the same Output Item limited to Shaped Recipes.
+ */
+ public static long DELETE_ALL_OTHER_SHAPED_RECIPES = B[8];
+ /**
+ * Removes all Recipes with the same Output Item limited to native Recipe Handlers.
+ */
+ public static long DELETE_ALL_OTHER_NATIVE_RECIPES = B[9];
+ /**
+ * Disables the check for colliding Recipes.
+ */
+ public static long DO_NOT_CHECK_FOR_COLLISIONS = B[10];
+ /**
+ * Only adds the Recipe if there is another Recipe having that Output
+ */
+ public static long ONLY_ADD_IF_THERE_IS_ANOTHER_RECIPE_FOR_IT = B[11];
+ /**
+ * Only adds the Recipe if it has an Output
+ */
+ public static long ONLY_ADD_IF_RESULT_IS_NOT_NULL = B[12];
+ /**
+ * Don't remove shapeless recipes with this output
+ */
+ public static long DONT_REMOVE_SHAPELESS = B[13];
+ }
+
+ /**
+ * Copy of the original Helper Class of Thermal Expansion, just to make sure it works even when other Mods include
+ * TE-APIs
+ */
+ public static class ThermalExpansion {
+
+ public static void addFurnaceRecipe(int energy, ItemStack input, ItemStack output) {}
+
+ public static void addPulverizerRecipe(int energy, ItemStack input, ItemStack primaryOutput) {}
+
+ public static void addPulverizerRecipe(int energy, ItemStack input, ItemStack primaryOutput,
+ ItemStack secondaryOutput) {}
+
+ public static void addPulverizerRecipe(int energy, ItemStack input, ItemStack primaryOutput,
+ ItemStack secondaryOutput, int secondaryChance) {}
+
+ public static void addSawmillRecipe(int energy, ItemStack input, ItemStack primaryOutput) {}
+
+ public static void addSawmillRecipe(int energy, ItemStack input, ItemStack primaryOutput,
+ ItemStack secondaryOutput) {}
+
+ public static void addSawmillRecipe(int energy, ItemStack input, ItemStack primaryOutput,
+ ItemStack secondaryOutput, int secondaryChance) {}
+
+ public static void addSmelterRecipe(int energy, ItemStack primaryInput, ItemStack secondaryInput,
+ ItemStack primaryOutput) {}
+
+ public static void addSmelterRecipe(int energy, ItemStack primaryInput, ItemStack secondaryInput,
+ ItemStack primaryOutput, ItemStack secondaryOutput) {}
+
+ public static void addSmelterRecipe(int energy, ItemStack primaryInput, ItemStack secondaryInput,
+ ItemStack primaryOutput, ItemStack secondaryOutput, int secondaryChance) {}
+
+ public static void addSmelterBlastOre(Materials aMaterial) {}
+
+ public static void addCrucibleRecipe(int energy, ItemStack input, FluidStack output) {}
+
+ public static void addTransposerFill(int energy, ItemStack input, ItemStack output, FluidStack fluid,
+ boolean reversible) {}
+
+ public static void addTransposerExtract(int energy, ItemStack input, ItemStack output, FluidStack fluid,
+ int chance, boolean reversible) {}
+
+ public static void addMagmaticFuel(String fluidName, int energy) {}
+
+ public static void addCompressionFuel(String fluidName, int energy) {}
+
+ public static void addCoolant(String fluidName, int energy) {}
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Multiblock_Tooltip_Builder.java b/src/main/java/gregtech/api/util/GT_Multiblock_Tooltip_Builder.java
new file mode 100644
index 0000000000..43eeccdc9f
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Multiblock_Tooltip_Builder.java
@@ -0,0 +1,720 @@
+package gregtech.api.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+
+/**
+ * This makes it easier to build multi tooltips, with a standardized format. <br>
+ * Info section order should be:<br>
+ * addMachineType<br>
+ * addInfo, for what it does, special notes, etc.<br>
+ * addSeparator, if you need it<br>
+ * addPollutionAmount<br>
+ * <br>
+ * Structure order should be:<br>
+ * beginStructureBlock<br>
+ * addController<br>
+ * addCasingInfo<br>
+ * addOtherStructurePart, for secondary structure block info (pipes, coils, etc)<br>
+ * addEnergyHatch/addDynamoHatch<br>
+ * addMaintenanceHatch<br>
+ * addMufflerHatch<br>
+ * addInputBus/addInputHatch/addOutputBus/addOutputHatch, in that order<br>
+ * Use addStructureInfo for any comments on nonstandard structure info wherever needed <br>
+ * toolTipFinisher goes at the very end<br>
+ * <br>
+ * Originally created by kekzdealer
+ */
+public class GT_Multiblock_Tooltip_Builder {
+
+ private static final String TAB = " ";
+ private static final String COLON = ": ";
+ private static final String SEPARATOR = ", ";
+
+ private final List<String> iLines;
+ private final List<String> sLines;
+ private final List<String> hLines;
+ private final SetMultimap<Integer, String> hBlocks;
+
+ private String[] iArray;
+ private String[] sArray;
+ private String[] hArray;
+
+ // Localized tooltips
+ private static final String TT_machineType = StatCollector.translateToLocal("GT5U.MBTT.MachineType");
+ private static final String TT_dimensions = StatCollector.translateToLocal("GT5U.MBTT.Dimensions");
+ private static final String TT_hollow = StatCollector.translateToLocal("GT5U.MBTT.Hollow");
+ private static final String TT_structure = StatCollector.translateToLocal("GT5U.MBTT.Structure");
+ private static final String TT_controller = StatCollector.translateToLocal("GT5U.MBTT.Controller");
+ private static final String TT_minimum = StatCollector.translateToLocal("GT5U.MBTT.Minimum");
+ private static final String TT_tiered = StatCollector.translateToLocal("GT5U.MBTT.Tiered");
+ private static final String TT_maintenancehatch = StatCollector.translateToLocal("GT5U.MBTT.MaintenanceHatch");
+ private static final String TT_energyhatch = StatCollector.translateToLocal("GT5U.MBTT.EnergyHatch");
+ private static final String TT_dynamohatch = StatCollector.translateToLocal("GT5U.MBTT.DynamoHatch");
+ private static final String TT_mufflerhatch = StatCollector.translateToLocal("GT5U.MBTT.MufflerHatch");
+ private static final String TT_inputbus = StatCollector.translateToLocal("GT5U.MBTT.InputBus");
+ private static final String TT_inputhatch = StatCollector.translateToLocal("GT5U.MBTT.InputHatch");
+ private static final String TT_outputbus = StatCollector.translateToLocal("GT5U.MBTT.OutputBus");
+ private static final String TT_outputhatch = StatCollector.translateToLocal("GT5U.MBTT.OutputHatch");
+ private static final String TT_causes = StatCollector.translateToLocal("GT5U.MBTT.Causes");
+ private static final String TT_pps = StatCollector.translateToLocal("GT5U.MBTT.PPS");
+ private static final String TT_hold = StatCollector.translateToLocal("GT5U.MBTT.Hold");
+ private static final String TT_todisplay = StatCollector.translateToLocal("GT5U.MBTT.Display");
+ private static final String TT_structurehint = StatCollector.translateToLocal("GT5U.MBTT.StructureHint");
+ private static final String TT_mod = StatCollector.translateToLocal("GT5U.MBTT.Mod");
+ private static final String TT_air = StatCollector.translateToLocal("GT5U.MBTT.Air");
+ private static final String[] TT_dots = IntStream.range(0, 16)
+ .mapToObj(i -> StatCollector.translateToLocal("structurelib.blockhint." + i + ".name"))
+ .toArray(String[]::new);
+
+ public GT_Multiblock_Tooltip_Builder() {
+ iLines = new LinkedList<>();
+ sLines = new LinkedList<>();
+ hLines = new LinkedList<>();
+ hBlocks = Multimaps.newSetMultimap(new HashMap<>(), HashSet::new);
+ hBlocks.put(StructureLibAPI.HINT_BLOCK_META_AIR, TT_air);
+ }
+
+ /**
+ * Add a line telling you what the machine type is. Usually, this will be the name of a SB version.<br>
+ * Machine Type: machine
+ *
+ * @param machine Name of the machine type
+ *
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addMachineType(String machine) {
+ iLines.add(TT_machineType + COLON + EnumChatFormatting.YELLOW + machine + EnumChatFormatting.RESET);
+ return this;
+ }
+
+ /**
+ * Add a basic line of information about this structure
+ *
+ * @param info The line to be added.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addInfo(String info) {
+ iLines.add(info);
+ return this;
+ }
+
+ /**
+ * Add a separator line like this:<br>
+ * -----------------------------------------
+ *
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addSeparator() {
+ iLines.add("-----------------------------------------");
+ return this;
+ }
+
+ /**
+ * Add a line telling how much this machine pollutes.
+ *
+ * @param pollution Amount of pollution per second when active
+ *
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addPollutionAmount(int pollution) {
+ iLines.add(
+ TT_causes + COLON + EnumChatFormatting.DARK_PURPLE + pollution + " " + EnumChatFormatting.GRAY + TT_pps);
+ return this;
+ }
+
+ /**
+ * Begin adding structural information by adding a line about the structure's dimensions and then inserting a
+ * "Structure:" line.
+ *
+ * @param w Structure width.
+ * @param h Structure height.
+ * @param l Structure depth/length.
+ * @param hollow T/F, adds a (hollow) comment if true
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder beginStructureBlock(int w, int h, int l, boolean hollow) {
+ sLines.add(
+ EnumChatFormatting.WHITE + TT_dimensions
+ + COLON
+ + EnumChatFormatting.GOLD
+ + w
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + h
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + l
+ + EnumChatFormatting.GRAY
+ + " ("
+ + EnumChatFormatting.GOLD
+ + "W"
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + "H"
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + "L"
+ + EnumChatFormatting.GRAY
+ + ") "
+ + EnumChatFormatting.RED
+ + (hollow ? EnumChatFormatting.RED + TT_hollow : ""));
+ sLines.add(EnumChatFormatting.WHITE + TT_structure + COLON);
+ return this;
+ }
+
+ /**
+ * Begin adding structural information by adding a line about the structure's dimensions<br>
+ * and then inserting a "Structure:" line. Variable version displays min and max
+ *
+ * @param wmin Structure min width.
+ * @param wmax Structure max width.
+ * @param hmin Structure min height.
+ * @param hmax Structure max height.
+ * @param lmin Structure min depth/length.
+ * @param lmax Structure max depth/length.
+ * @param hollow T/F, adds a (hollow) comment if true
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder beginVariableStructureBlock(int wmin, int wmax, int hmin, int hmax, int lmin,
+ int lmax, boolean hollow) {
+ sLines.add(
+ EnumChatFormatting.WHITE + TT_dimensions
+ + COLON
+ + EnumChatFormatting.GOLD
+ + wmin
+ + (wmin != wmax ? "-" + wmax : "")
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + hmin
+ + (hmin != hmax ? "-" + hmax : "")
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + lmin
+ + (lmin != lmax ? "-" + lmax : "")
+ + EnumChatFormatting.GRAY
+ + " ("
+ + EnumChatFormatting.GOLD
+ + "W"
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + "H"
+ + EnumChatFormatting.GRAY
+ + "x"
+ + EnumChatFormatting.GOLD
+ + "L"
+ + EnumChatFormatting.GRAY
+ + ") "
+ + (hollow ? EnumChatFormatting.RED + TT_hollow : ""));
+ sLines.add(EnumChatFormatting.WHITE + TT_structure + COLON);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Controller: info
+ *
+ * @param info Positional information.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addController(String info) {
+ sLines.add(TAB + EnumChatFormatting.WHITE + TT_controller + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)minCountx casingName (minimum) (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param minCount Minimum needed for valid structure check.
+ * @return Instance this method was called on.
+ *
+ * @deprecated Replaced by {@link #addCasingInfoMin(String, int, boolean)}
+ *
+ */
+ @Deprecated
+ public GT_Multiblock_Tooltip_Builder addCasingInfo(String casingName, int minCount) {
+ return addCasingInfoMin(casingName, minCount, false);
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)countx casingName (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoExactly(String casingName, int count, boolean isTiered) {
+ return addCasingInfoExactlyColored(
+ casingName,
+ EnumChatFormatting.GRAY,
+ count,
+ EnumChatFormatting.GOLD,
+ isTiered);
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)countx casingName (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @param countColor Color of the casing count text
+ * @param textColor Color of the casing name text
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoExactlyColored(String casingName, EnumChatFormatting textColor,
+ int count, EnumChatFormatting countColor, boolean isTiered) {
+ sLines.add(
+ countColor + TAB
+ + count
+ + "x "
+ + EnumChatFormatting.RESET
+ + textColor
+ + casingName
+ + (isTiered ? " " + TT_tiered : ""));
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)minCountx casingName (minimum) (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param minCount Minimum needed for valid structure check.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoMin(String casingName, int minCount, boolean isTiered) {
+ return addCasingInfoMinColored(
+ casingName,
+ EnumChatFormatting.GRAY,
+ minCount,
+ EnumChatFormatting.GOLD,
+ isTiered);
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)minCountx casingName (minimum) (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param minCount Minimum needed for valid structure check.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @param countColor Color of the casing count text
+ * @param textColor Color of the casing name text
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoMinColored(String casingName, EnumChatFormatting textColor,
+ int minCount, EnumChatFormatting countColor, boolean isTiered) {
+ sLines.add(
+ countColor + TAB
+ + minCount
+ + "x "
+ + EnumChatFormatting.RESET
+ + textColor
+ + casingName
+ + " "
+ + TT_minimum
+ + (isTiered ? " " + TT_tiered : ""));
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)minCountx - maxCountx casingName (minimum) (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param minCount Minimum needed for valid structure check.
+ * @param maxCount Maximum needed for valid structure check.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoRange(String casingName, int minCount, int maxCount,
+ boolean isTiered) {
+ return addCasingInfoRangeColored(
+ casingName,
+ EnumChatFormatting.GRAY,
+ minCount,
+ maxCount,
+ EnumChatFormatting.GOLD,
+ isTiered);
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)minCountx - maxCountx casingName (minimum) (tiered)
+ *
+ * @param casingName Name of the Casing.
+ * @param minCount Minimum needed for valid structure check.
+ * @param maxCount Maximum needed for valid structure check.
+ * @param isTiered Flag if this casing accepts multiple tiers (e.g. coils)
+ * @param countColor Color of the casing count text
+ * @param textColor Color of the casing name text
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addCasingInfoRangeColored(String casingName, EnumChatFormatting textColor,
+ int minCount, int maxCount, EnumChatFormatting countColor, boolean isTiered) {
+ sLines.add(
+ countColor + TAB
+ + minCount
+ + "x"
+ + EnumChatFormatting.GRAY
+ + " - "
+ + countColor
+ + maxCount
+ + "x "
+ + EnumChatFormatting.RESET
+ + textColor
+ + casingName
+ + (isTiered ? " " + TT_tiered : ""));
+ return this;
+ }
+
+ /**
+ * Use this method to add a structural part that isn't covered by the other methods.<br>
+ * (indent)name: info
+ *
+ * @param name Name of the hatch or other component.
+ * @param info Positional information.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOtherStructurePart(String name, String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + name + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Maintenance Hatch: info
+ *
+ * @param info Positional information.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addMaintenanceHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_maintenancehatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Muffler Hatch: info
+ *
+ * @param info Location where the hatch goes
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addMufflerHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_mufflerhatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Energy Hatch: info
+ *
+ * @param info Positional information.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addEnergyHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_energyhatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Dynamo Hatch: info
+ *
+ * @param info Positional information.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addDynamoHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_dynamohatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Input Bus: info
+ *
+ * @param info Location where the bus goes
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addInputBus(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_inputbus + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Input Hatch: info
+ *
+ * @param info Location where the hatch goes
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addInputHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_inputhatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Output Bus: info
+ *
+ * @param info Location where the bus goes
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOutputBus(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_outputbus + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Output Hatch: info
+ *
+ * @param info Location where the bus goes
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOutputHatch(String info) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_outputhatch + COLON + EnumChatFormatting.GRAY + info);
+ return this;
+ }
+
+ /**
+ * Use this method to add a structural part that isn't covered by the other methods.<br>
+ * (indent)name: info
+ *
+ * @param name Name of the hatch or other component.
+ * @param info Positional information.
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOtherStructurePart(String name, String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + name + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, name);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Maintenance Hatch: info
+ *
+ * @param info Positional information.
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addMaintenanceHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_maintenancehatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_maintenancehatch);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Muffler Hatch: info
+ *
+ * @param info Location where the hatch goes
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addMufflerHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_mufflerhatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_mufflerhatch);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Energy Hatch: info
+ *
+ * @param info Positional information.
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addEnergyHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_energyhatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_energyhatch);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Dynamo Hatch: info
+ *
+ * @param info Positional information.
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addDynamoHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_dynamohatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_dynamohatch);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Input Bus: info
+ *
+ * @param info Location where the bus goes
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addInputBus(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_inputbus + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_inputbus);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Input Hatch: info
+ *
+ * @param info Location where the hatch goes
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addInputHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_inputhatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_inputhatch);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Output Bus: info
+ *
+ * @param info Location where the bus goes
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOutputBus(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_outputbus + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_outputbus);
+ return this;
+ }
+
+ /**
+ * Add a line of information about the structure:<br>
+ * (indent)Output Hatch: info
+ *
+ * @param info Location where the bus goes
+ * @param dots The valid locations for this part when asked to display hints
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addOutputHatch(String info, int... dots) {
+ sLines.add(EnumChatFormatting.WHITE + TAB + TT_outputhatch + COLON + EnumChatFormatting.GRAY + info);
+ for (int dot : dots) hBlocks.put(dot, TT_outputhatch);
+ return this;
+ }
+
+ /**
+ * Use this method to add non-standard structural info.<br>
+ * (indent)info
+ *
+ * @param info The line to be added.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addStructureInfo(String info) {
+ sLines.add(TAB + info);
+ return this;
+ }
+
+ /**
+ * Use this method to add non-standard structural info.<br>
+ * (indent)info
+ *
+ * @param channel the name of subchannel
+ * @param purpose the purpose of subchannel
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addSubChannelUsage(String channel, String purpose) {
+ sLines.add(TAB + StatCollector.translateToLocalFormatted("GT5U.MBTT.subchannel", channel, purpose));
+ return this;
+ }
+
+ /**
+ * Use this method to add non-standard structural hint. This info will appear before the standard structural hint.
+ *
+ * @param info The line to be added. This should be an entry into minecraft's localization system.
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addStructureHint(String info) {
+ hLines.add(StatCollector.translateToLocal(info));
+ return this;
+ }
+
+ /**
+ * Use this method to add an entry to standard structural hint without creating a corresponding line in structure
+ * information
+ *
+ * @param name The name of block This should be an entry into minecraft's localization system.
+ * @param dots Possible locations of this block
+ * @return Instance this method was called on.
+ */
+ public GT_Multiblock_Tooltip_Builder addStructureHint(String name, int... dots) {
+ for (int dot : dots) hBlocks.put(dot, StatCollector.translateToLocal(name));
+ return this;
+ }
+
+ /**
+ * Call at the very end.<br>
+ * Adds a final line with the mod name and information on how to display the structure guidelines.<br>
+ * Ends the building process.
+ *
+ * @param mod Name of the mod that adds this multiblock machine
+ */
+ public void toolTipFinisher(String mod) {
+ iLines.add(
+ TT_hold + " "
+ + EnumChatFormatting.BOLD
+ + "[LSHIFT]"
+ + EnumChatFormatting.RESET
+ + EnumChatFormatting.GRAY
+ + " "
+ + TT_todisplay);
+ iLines.add(TT_mod + COLON + EnumChatFormatting.GREEN + mod + EnumChatFormatting.GRAY);
+ hLines.add(TT_structurehint);
+ iArray = iLines.toArray(new String[0]);
+ sArray = sLines.toArray(new String[0]);
+ // e.getKey() - 1 because 1 dot is meta 0.
+ hArray = Stream.concat(
+ hLines.stream(),
+ hBlocks.asMap()
+ .entrySet()
+ .stream()
+ .map(e -> TT_dots[e.getKey() - 1] + COLON + String.join(SEPARATOR, e.getValue())))
+ .toArray(String[]::new);
+ }
+
+ public String[] getInformation() {
+ return iArray;
+ }
+
+ public String[] getStructureInformation() {
+ return sArray;
+ }
+
+ public String[] getStructureHint() {
+ return hArray;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_OreDictUnificator.java b/src/main/java/gregtech/api/util/GT_OreDictUnificator.java
new file mode 100644
index 0000000000..7032fc87fc
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_OreDictUnificator.java
@@ -0,0 +1,570 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+import static gregtech.api.enums.GT_Values.M;
+import static gregtech.api.enums.GT_Values.W;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.oredict.OreDictionary;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.Dyes;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SubTag;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.objects.ItemData;
+import gregtech.api.objects.MaterialStack;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
+
+/**
+ * NEVER INCLUDE THIS FILE IN YOUR MOD!!!
+ * <p/>
+ * This is the Core of my OreDict Unification Code
+ * <p/>
+ * If you just want to use this to unificate your Items, then use the Function in the GregTech_API File
+ * <p/>
+ * P.S. It is intended to be named "Unificator" and not "Unifier", because that sounds more awesome.
+ */
+public class GT_OreDictUnificator {
+
+ private static final Map<String, ItemStack> sName2StackMap = new HashMap<>();
+ private static final Map<ItemStack, ItemData> sItemStack2DataMap = new Object2ObjectOpenCustomHashMap<>(
+ GT_ItemStack.ITEMSTACK_HASH_STRATEGY2);
+ private static final Map<ItemStack, List<ItemStack>> sUnificationTable = new Object2ObjectOpenCustomHashMap<>(
+ GT_ItemStack.ITEMSTACK_HASH_STRATEGY2);
+ private static final Set<ItemStack> sNoUnificationList = new ObjectOpenCustomHashSet<>(
+ GT_ItemStack.ITEMSTACK_HASH_STRATEGY2);
+ private static int isRegisteringOre = 0, isAddingOre = 0;
+ private static boolean mRunThroughTheList = true;
+
+ static {
+ GregTech_API.sItemStackMappings.add(sItemStack2DataMap);
+ GregTech_API.sItemStackMappings.add(sUnificationTable);
+ }
+
+ /**
+ * The Blacklist just prevents the Item from being unificated into something else. Useful if you have things like
+ * the Industrial Diamond, which is better than regular Diamond, but also usable in absolutely all Diamond Recipes.
+ */
+ public static void addToBlacklist(ItemStack aStack) {
+ if (GT_Utility.isStackValid(aStack) && !GT_Utility.isStackInStackSet(aStack, sNoUnificationList))
+ sNoUnificationList.add(aStack);
+ }
+
+ public static boolean isBlacklisted(ItemStack aStack) {
+ return GT_Utility.isStackInStackSet(aStack, sNoUnificationList);
+ }
+
+ public static void add(OrePrefixes aPrefix, Materials aMaterial, ItemStack aStack) {
+ set(aPrefix, aMaterial, aStack, false, false);
+ }
+
+ public static void set(OrePrefixes aPrefix, Materials aMaterial, ItemStack aStack) {
+ set(aPrefix, aMaterial, aStack, true, false);
+ }
+
+ public static void set(OrePrefixes aPrefix, Materials aMaterial, ItemStack aStack, boolean aOverwrite,
+ boolean aAlreadyRegistered) {
+ if (aMaterial == null || aPrefix == null
+ || GT_Utility.isStackInvalid(aStack)
+ || Items.feather.getDamage(aStack) == W) return;
+ isAddingOre++;
+ aStack = GT_Utility.copyAmount(1, aStack);
+ if (!aAlreadyRegistered) registerOre(aPrefix.get(aMaterial), aStack);
+ addAssociation(aPrefix, aMaterial, aStack, isBlacklisted(aStack));
+ if (aOverwrite || GT_Utility.isStackInvalid(
+ sName2StackMap.get(
+ aPrefix.get(aMaterial)
+ .toString())))
+ sName2StackMap.put(
+ aPrefix.get(aMaterial)
+ .toString(),
+ aStack);
+ isAddingOre--;
+ }
+
+ public static ItemStack getFirstOre(Object aName, long aAmount) {
+ if (GT_Utility.isStringInvalid(aName)) return null;
+ ItemStack tStack = sName2StackMap.get(aName.toString());
+ if (GT_Utility.isStackValid(tStack)) return GT_Utility.copyAmount(aAmount, tStack);
+ return GT_Utility.copyAmount(aAmount, getOresImmutable(aName).toArray());
+ }
+
+ public static ItemStack get(Object aName, long aAmount) {
+ return get(aName, null, aAmount, true, true);
+ }
+
+ public static ItemStack get(Object aName, ItemStack aReplacement, long aAmount) {
+ return get(aName, aReplacement, aAmount, true, true);
+ }
+
+ public static ItemStack get(OrePrefixes aPrefix, Object aMaterial, long aAmount) {
+ return get(aPrefix, aMaterial, null, aAmount);
+ }
+
+ public static ItemStack get(OrePrefixes aPrefix, Object aMaterial, ItemStack aReplacement, long aAmount) {
+ if (OrePrefixes.mPreventableComponents.contains(aPrefix) && aPrefix.mDisabledItems.contains(aMaterial))
+ return aReplacement;
+ return get(aPrefix.get(aMaterial), aReplacement, aAmount, false, true);
+ }
+
+ public static ItemStack get(OrePrefixes aPrefix, Object aMaterial, long aAmount, boolean aNoInvalidAmounts) {
+ if (OrePrefixes.mPreventableComponents.contains(aPrefix) && aPrefix.mDisabledItems.contains(aMaterial))
+ return null;
+ return get(aPrefix.get(aMaterial), null, aAmount, false, aNoInvalidAmounts);
+ }
+
+ public static ItemStack get(Object aName, ItemStack aReplacement, long aAmount, boolean aMentionPossibleTypos,
+ boolean aNoInvalidAmounts) {
+ if (aNoInvalidAmounts && aAmount < 1) return null;
+ final ItemStack stackFromName = sName2StackMap.get(aName.toString());
+ if (stackFromName != null) return GT_Utility.copyAmount(aAmount, stackFromName);
+ if (aMentionPossibleTypos) {
+ GT_Log.err.println("Unknown Key for Unification, Typo? " + aName);
+ }
+ final ItemStack stackFirstOre = getFirstOre(aName, aAmount);
+ if (stackFirstOre != null) return GT_Utility.copyAmount(aAmount, stackFirstOre);
+ return GT_Utility.copyAmount(aAmount, aReplacement);
+ }
+
+ public static ItemStack[] setStackArray(boolean aUseBlackList, ItemStack... aStacks) {
+ for (int i = 0; i < aStacks.length; i++) aStacks[i] = get(aUseBlackList, GT_Utility.copyOrNull(aStacks[i]));
+ return aStacks;
+ }
+
+ public static ItemStack[] getStackArray(boolean aUseBlackList, Object... aStacks) {
+ ItemStack[] rStacks = new ItemStack[aStacks.length];
+ for (int i = 0; i < aStacks.length; i++) {
+ rStacks[i] = get(aUseBlackList, GT_Utility.copy(aStacks[i]), true);
+ }
+ return rStacks;
+ }
+
+ public static ItemStack setStack(ItemStack aStack) {
+ return setStack(true, aStack);
+ }
+
+ public static ItemStack setStack(boolean aUseBlackList, ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return aStack;
+ ItemStack tStack = get(aUseBlackList, aStack);
+ if (GT_Utility.areStacksEqual(aStack, tStack)) return aStack;
+ aStack.func_150996_a(tStack.getItem());
+ Items.feather.setDamage(aStack, Items.feather.getDamage(tStack));
+ return aStack;
+ }
+
+ public static ItemStack get(ItemStack aStack) {
+ return get(true, aStack);
+ }
+
+ public static ItemStack get(boolean aUseBlackList, ItemStack aStack) {
+ return get(aUseBlackList, aStack, false);
+ }
+
+ /**
+ * @param unsafe If true, it does not limit stack size by 64.
+ */
+ public static ItemStack get(boolean aUseBlackList, ItemStack aStack, boolean unsafe) {
+ if (GT_Utility.isStackInvalid(aStack)) return null;
+ ItemData tPrefixMaterial = getAssociation(aStack);
+ if (tPrefixMaterial == null || !tPrefixMaterial.hasValidPrefixMaterialData()
+ || (aUseBlackList && tPrefixMaterial.mBlackListed)) return GT_Utility.copyOrNull(aStack);
+ if (aUseBlackList && !GregTech_API.sUnificationEntriesRegistered && isBlacklisted(aStack)) {
+ tPrefixMaterial.mBlackListed = true;
+ return GT_Utility.copyOrNull(aStack);
+ }
+ if (tPrefixMaterial.mUnificationTarget == null)
+ tPrefixMaterial.mUnificationTarget = sName2StackMap.get(tPrefixMaterial.toString());
+ ItemStack rStack = tPrefixMaterial.mUnificationTarget;
+ if (GT_Utility.isStackInvalid(rStack)) return GT_Utility.copyOrNull(aStack);
+ ItemStack newStack;
+ if (unsafe) {
+ newStack = GT_Utility.copyAmountUnsafe(aStack.stackSize, rStack);
+ } else {
+ newStack = GT_Utility.copyAmount(aStack.stackSize, rStack);
+ }
+ // NBT is assigned by reference here, so mutating it may have unexpected side effects.
+ newStack.setTagCompound(aStack.getTagCompound());
+ return newStack;
+ }
+
+ /**
+ * Doesn't copy the returned stack or set quantity. Be careful and do not mutate it; intended only to optimize
+ * comparisons
+ */
+ public static ItemStack get_nocopy(ItemStack aStack) {
+ return get_nocopy(true, aStack);
+ }
+
+ /**
+ * Doesn't copy the returned stack or set quantity. Be careful and do not mutate it; intended only to optimize
+ * comparisons
+ */
+ static ItemStack get_nocopy(boolean aUseBlackList, ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return null;
+ ItemData tPrefixMaterial = getAssociation(aStack);
+ if (tPrefixMaterial == null || !tPrefixMaterial.hasValidPrefixMaterialData()
+ || (aUseBlackList && tPrefixMaterial.mBlackListed)) return aStack;
+ if (aUseBlackList && !GregTech_API.sUnificationEntriesRegistered && isBlacklisted(aStack)) {
+ tPrefixMaterial.mBlackListed = true;
+ return aStack;
+ }
+ if (tPrefixMaterial.mUnificationTarget == null)
+ tPrefixMaterial.mUnificationTarget = sName2StackMap.get(tPrefixMaterial.toString());
+ ItemStack rStack = tPrefixMaterial.mUnificationTarget;
+ if (GT_Utility.isStackInvalid(rStack)) return aStack;
+
+ // Yes, == and not .equals().
+ // This check is primarily intended to optimize for the case where both rStack and aStack
+ // do not have NBT, and so we would be comparing null == null.
+ //
+ // Even if aStack and rStack may have equal NBT, we prefer to do an inexpensive
+ // new ItemStack() over the potentially expensive NBTTagCompound.equals().
+ if (aStack.getTagCompound() == rStack.getTagCompound()) {
+ // Warning: rStack's stack size may not be equal to aStack's stack size.
+ return rStack;
+ }
+
+ // Okay, okay, I lied, we actually do need to make a copy.
+ // This is to fix a long-standing bug where we were mutating NBT directly on rStack,
+ // which had unexpected and unpredictable ripple effects.
+ //
+ // We will do some custom copying here, to avoid ItemStack.copy(),
+ // which calls the potentially expensive NBTTagCompound.copy()
+ // NBT is assigned by reference here, so mutating it may have unexpected side effects.
+ ItemStack newStack = new ItemStack(rStack.getItem(), aStack.stackSize, Items.feather.getDamage(rStack));
+ newStack.setTagCompound(aStack.getTagCompound());
+ return newStack;
+ }
+
+ /**
+ * Compares the first argument against an already-unificated second argument as if aUseBlackList was both true and
+ * false.
+ */
+ public static boolean isInputStackEqual(ItemStack aStack, ItemStack unified_tStack) {
+ boolean alreadyCompared = false;
+ if (GT_Utility.isStackInvalid(aStack)) return false;
+ ItemData tPrefixMaterial = getAssociation(aStack);
+ ItemStack rStack = null;
+ if (tPrefixMaterial == null || !tPrefixMaterial.hasValidPrefixMaterialData())
+ return GT_Utility.areStacksEqual(aStack, unified_tStack, true);
+ else if (tPrefixMaterial.mBlackListed) {
+ if (GT_Utility.areStacksEqual(aStack, unified_tStack, true)) return true;
+ else alreadyCompared = true;
+ }
+ if (!alreadyCompared && !GregTech_API.sUnificationEntriesRegistered && isBlacklisted(aStack)) {
+ tPrefixMaterial.mBlackListed = true;
+ if (GT_Utility.areStacksEqual(aStack, unified_tStack, true)) return true;
+ else alreadyCompared = true;
+ }
+ if (tPrefixMaterial.mUnificationTarget == null)
+ tPrefixMaterial.mUnificationTarget = sName2StackMap.get(tPrefixMaterial.toString());
+ rStack = tPrefixMaterial.mUnificationTarget;
+ if (GT_Utility.isStackInvalid(rStack))
+ return !alreadyCompared && GT_Utility.areStacksEqual(aStack, unified_tStack, true);
+ return GT_Utility.areStacksEqual(rStack, unified_tStack, true);
+ }
+
+ public static List<ItemStack> getNonUnifiedStacks(Object obj) {
+ 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 (ItemStack tGTStack0 : sItemStack2DataMap.keySet()) {
+ ItemStack tStack0 = GT_ItemStack.internalCopyStack(tGTStack0);
+ ItemStack tStack1 = get_nocopy(false, tStack0);
+ if (!GT_Utility.areStacksEqual(tStack0, tStack1)) {
+ List<ItemStack> list = sUnificationTable.computeIfAbsent(tStack1, 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 };
+ else if (obj instanceof ItemStack[]) aStacks = (ItemStack[]) obj;
+ else if (obj instanceof List) aStacks = (ItemStack[]) ((List<?>) obj).toArray(new ItemStack[0]);
+ List<ItemStack> rList = new ArrayList<>();
+ for (ItemStack aStack : aStacks) {
+ if (aStack == null) continue;
+ rList.add(aStack);
+ List<ItemStack> tList = sUnificationTable.get(aStack);
+ if (tList != null) {
+ for (ItemStack tStack : tList) {
+ ItemStack tStack1 = GT_Utility.copyAmount(aStack.stackSize, tStack);
+ rList.add(tStack1);
+ }
+ }
+ }
+ return rList;
+ }
+
+ public static void addItemData(ItemStack aStack, ItemData aData) {
+ if (GT_Utility.isStackValid(aStack) && getItemData(aStack) == null && aData != null) setItemData(aStack, aData);
+ }
+
+ public static void addItemDataFromInputs(ItemStack output, Object... inputs) {
+ int length = inputs.length;
+ ItemData[] tData = new ItemData[length];
+ for (int i = 0; i < length; i++) {
+ if (inputs[i] instanceof ItemStack) {
+ tData[i] = GT_OreDictUnificator.getItemData((ItemStack) inputs[i]);
+ } else if (inputs[i] instanceof ItemData) {
+ tData[i] = (ItemData) inputs[i];
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ if (GT_Utility.arrayContainsNonNull(tData)) {
+ GT_OreDictUnificator.addItemData(output, new ItemData(tData));
+ }
+ }
+
+ public static void setItemData(ItemStack aStack, ItemData aData) {
+ if (GT_Utility.isStackInvalid(aStack) || aData == null) return;
+ ItemData tData = getItemData(aStack);
+ if (tData == null || !tData.hasValidPrefixMaterialData()) {
+ if (tData != null) for (Object tObject : tData.mExtraData)
+ if (!aData.mExtraData.contains(tObject)) aData.mExtraData.add(tObject);
+ if (aStack.stackSize > 1) {
+ if (aData.mMaterial != null) aData.mMaterial.mAmount /= aStack.stackSize;
+ for (MaterialStack tMaterial : aData.mByProducts) tMaterial.mAmount /= aStack.stackSize;
+ aStack = GT_Utility.copyAmount(1, aStack);
+ }
+ sItemStack2DataMap.put(aStack, aData);
+ if (aData.hasValidMaterialData()) {
+ long tValidMaterialAmount = aData.mMaterial.mMaterial.contains(SubTag.NO_RECYCLING) ? 0
+ : aData.mMaterial.mAmount >= 0 ? aData.mMaterial.mAmount : M;
+ for (MaterialStack tMaterial : aData.mByProducts)
+ tValidMaterialAmount += tMaterial.mMaterial.contains(SubTag.NO_RECYCLING) ? 0
+ : tMaterial.mAmount >= 0 ? tMaterial.mAmount : M;
+ if (tValidMaterialAmount < M) GT_ModHandler.addToRecyclerBlackList(aStack);
+ }
+ if (mRunThroughTheList) {
+ if (GregTech_API.sLoadStarted) {
+ mRunThroughTheList = false;
+ for (Entry<ItemStack, ItemData> tEntry : sItemStack2DataMap.entrySet()) if (!tEntry.getValue()
+ .hasValidPrefixData() || tEntry.getValue().mPrefix.mAllowNormalRecycling)
+ GT_RecipeRegistrator.registerMaterialRecycling(
+ GT_ItemStack.internalCopyStack(tEntry.getKey()),
+ tEntry.getValue());
+ }
+ } else {
+ if (!aData.hasValidPrefixData() || aData.mPrefix.mAllowNormalRecycling)
+ GT_RecipeRegistrator.registerMaterialRecycling(aStack, aData);
+ }
+ } else {
+ for (Object tObject : aData.mExtraData)
+ if (!tData.mExtraData.contains(tObject)) tData.mExtraData.add(tObject);
+ }
+ }
+
+ public static void removeItemData(ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) {
+ return;
+ }
+ sItemStack2DataMap.remove(aStack);
+ }
+
+ public static void addAssociation(OrePrefixes aPrefix, Materials aMaterial, ItemStack aStack,
+ boolean aBlackListed) {
+ if (aPrefix == null || aMaterial == null || GT_Utility.isStackInvalid(aStack)) return;
+ if (Items.feather.getDamage(aStack) == W) for (byte i = 0; i < 16; i++)
+ setItemData(GT_Utility.copyAmountAndMetaData(1, i, aStack), new ItemData(aPrefix, aMaterial, aBlackListed));
+ setItemData(aStack, new ItemData(aPrefix, aMaterial, aBlackListed));
+ }
+
+ @Nullable
+ public static ItemData getItemData(ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return null;
+ ItemData rData = sItemStack2DataMap.get(aStack);
+ if (rData == null) { // Try the lookup again but with wildcard damage value
+ rData = sItemStack2DataMap.get(GT_ItemStack.internalCopyStack(aStack, true));
+ }
+ return rData;
+ }
+
+ @Nullable
+ public static ItemData getAssociation(ItemStack aStack) {
+ ItemData rData = getItemData(aStack);
+ return rData != null && rData.hasValidPrefixMaterialData() ? rData : null;
+ }
+
+ public static boolean isItemStackInstanceOf(ItemStack aStack, Object aName) {
+ if (GT_Utility.isStringInvalid(aName) || GT_Utility.isStackInvalid(aStack)) return false;
+ for (ItemStack tOreStack : getOresImmutable(aName.toString()))
+ if (GT_Utility.areStacksEqual(tOreStack, aStack, true)) return true;
+ return false;
+ }
+
+ public static boolean isItemStackDye(ItemStack aStack) {
+ if (GT_Utility.isStackInvalid(aStack)) return false;
+
+ for (Dyes tDye : Dyes.VALUES) if (isItemStackInstanceOf(aStack, tDye.toString())) return true;
+
+ return false;
+ }
+
+ public static boolean registerOre(OrePrefixes aPrefix, Object aMaterial, ItemStack aStack) {
+ return registerOre(aPrefix.get(aMaterial), aStack);
+ }
+
+ public static boolean registerOre(Object aName, ItemStack aStack) {
+ if (aName == null || GT_Utility.isStackInvalid(aStack)) return false;
+
+ String tName = aName.toString();
+
+ if (GT_Utility.isStringInvalid(tName)) return false;
+
+ for (ItemStack itemStack : getOresImmutable(tName))
+ if (GT_Utility.areStacksEqual(itemStack, aStack, true)) return false;
+
+ isRegisteringOre++;
+ OreDictionary.registerOre(tName, GT_Utility.copyAmount(1, aStack));
+ isRegisteringOre--;
+ return true;
+ }
+
+ public static boolean isRegisteringOres() {
+ return isRegisteringOre > 0;
+ }
+
+ public static boolean isAddingOres() {
+ return isAddingOre > 0;
+ }
+
+ public static void resetUnificationEntries() {
+ for (ItemData tPrefixMaterial : sItemStack2DataMap.values()) tPrefixMaterial.mUnificationTarget = null;
+ }
+
+ public static ItemStack getGem(MaterialStack aMaterial) {
+ return aMaterial == null ? null : getGem(aMaterial.mMaterial, aMaterial.mAmount);
+ }
+
+ public static ItemStack getGem(Materials aMaterial, OrePrefixes aPrefix) {
+ return aMaterial == null ? null : getGem(aMaterial, aPrefix.mMaterialAmount);
+ }
+
+ public static ItemStack getGem(Materials aMaterial, long aMaterialAmount) {
+ ItemStack rStack = null;
+ if (((aMaterialAmount >= M))) rStack = get(OrePrefixes.gem, aMaterial, aMaterialAmount / M);
+ if (rStack == null) {
+ if ((((aMaterialAmount * 2) % M == 0) || aMaterialAmount >= M * 16))
+ rStack = get(OrePrefixes.gemFlawed, aMaterial, (aMaterialAmount * 2) / M);
+ if ((((aMaterialAmount * 4) >= M)))
+ rStack = get(OrePrefixes.gemChipped, aMaterial, (aMaterialAmount * 4) / M);
+ }
+ return rStack;
+ }
+
+ public static ItemStack getDust(MaterialStack aMaterial) {
+ return aMaterial == null ? null : getDust(aMaterial.mMaterial, aMaterial.mAmount);
+ }
+
+ public static ItemStack getDust(Materials aMaterial, OrePrefixes aPrefix) {
+ return aMaterial == null ? null : getDust(aMaterial, aPrefix.mMaterialAmount);
+ }
+
+ public static ItemStack getDust(Materials aMaterial, long aMaterialAmount) {
+ if (aMaterialAmount <= 0) return null;
+ ItemStack rStack = null;
+ if (((aMaterialAmount % M == 0) || aMaterialAmount >= M * 16))
+ rStack = get(OrePrefixes.dust, aMaterial, aMaterialAmount / M);
+ if (rStack == null && (((aMaterialAmount * 4) % M == 0) || aMaterialAmount >= M * 8))
+ rStack = get(OrePrefixes.dustSmall, aMaterial, (aMaterialAmount * 4) / M);
+ if (rStack == null && (((aMaterialAmount * 9) >= M)))
+ rStack = get(OrePrefixes.dustTiny, aMaterial, (aMaterialAmount * 9) / M);
+ return rStack;
+ }
+
+ public static ItemStack getIngot(MaterialStack aMaterial) {
+ return aMaterial == null ? null : getIngot(aMaterial.mMaterial, aMaterial.mAmount);
+ }
+
+ public static ItemStack getIngot(Materials aMaterial, OrePrefixes aPrefix) {
+ return aMaterial == null ? null : getIngot(aMaterial, aPrefix.mMaterialAmount);
+ }
+
+ public static ItemStack getIngot(Materials aMaterial, long aMaterialAmount) {
+ if (aMaterialAmount <= 0) return null;
+ ItemStack rStack = null;
+ if (((aMaterialAmount % (M * 9) == 0 && aMaterialAmount / (M * 9) > 1) || aMaterialAmount >= M * 72))
+ rStack = get(OrePrefixes.block, aMaterial, aMaterialAmount / (M * 9));
+ if (rStack == null && ((aMaterialAmount % M == 0) || aMaterialAmount >= M * 8))
+ rStack = get(OrePrefixes.ingot, aMaterial, aMaterialAmount / M);
+ if (rStack == null && (((aMaterialAmount * 9) >= M)))
+ rStack = get(OrePrefixes.nugget, aMaterial, (aMaterialAmount * 9) / M);
+ return rStack;
+ }
+
+ public static ItemStack getIngotOrDust(Materials aMaterial, long aMaterialAmount) {
+ if (aMaterialAmount <= 0) return null;
+ ItemStack rStack = getIngot(aMaterial, aMaterialAmount);
+ if (rStack == null) rStack = getDust(aMaterial, aMaterialAmount);
+ return rStack;
+ }
+
+ public static ItemStack getIngotOrDust(MaterialStack aMaterial) {
+ ItemStack rStack = getIngot(aMaterial);
+ if (rStack == null) rStack = getDust(aMaterial);
+ return rStack;
+ }
+
+ public static ItemStack getDustOrIngot(Materials aMaterial, long aMaterialAmount) {
+ if (aMaterialAmount <= 0) return null;
+ ItemStack rStack = getDust(aMaterial, aMaterialAmount);
+ if (rStack == null) rStack = getIngot(aMaterial, aMaterialAmount);
+ return rStack;
+ }
+
+ public static ItemStack getDustOrIngot(MaterialStack aMaterial) {
+ ItemStack rStack = getDust(aMaterial);
+ if (rStack == null) rStack = getIngot(aMaterial);
+ return rStack;
+ }
+
+ /**
+ * @return a Copy of the OreDictionary.getOres() List
+ */
+ public static ArrayList<ItemStack> getOres(OrePrefixes aPrefix, Object aMaterial) {
+ return getOres(aPrefix.get(aMaterial));
+ }
+
+ /**
+ * @return a Copy of the OreDictionary.getOres() List
+ */
+ public static ArrayList<ItemStack> getOres(Object aOreName) {
+ String aName = aOreName == null ? E : aOreName.toString();
+ ArrayList<ItemStack> rList = new ArrayList<>();
+ if (GT_Utility.isStringValid(aName)) rList.addAll(OreDictionary.getOres(aName));
+ return rList;
+ }
+
+ /**
+ * Fast version of {@link #getOres(Object)}, which doesn't call
+ * {@link System#arraycopy(Object, int, Object, int, int)} in {@link ArrayList#addAll}
+ */
+ public static List<ItemStack> getOresImmutable(@Nullable Object aOreName) {
+ String aName = aOreName == null ? E : aOreName.toString();
+
+ return GT_Utility.isStringValid(aName) ? Collections.unmodifiableList(OreDictionary.getOres(aName))
+ : Collections.emptyList();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_OverclockCalculator.java b/src/main/java/gregtech/api/util/GT_OverclockCalculator.java
new file mode 100644
index 0000000000..609a196e80
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_OverclockCalculator.java
@@ -0,0 +1,631 @@
+package gregtech.api.util;
+
+import java.util.function.Supplier;
+
+import javax.annotation.Nonnull;
+
+import gregtech.api.enums.GT_Values;
+
+public class GT_OverclockCalculator {
+
+ private static final double LOG2 = Math.log(2);
+
+ /**
+ * Voltage the recipe will run at
+ */
+ private long recipeVoltage = 0;
+ /*
+ * The amount of amps the recipe needs
+ */
+ private long recipeAmperage = 1;
+ /**
+ * Voltage of the machine
+ */
+ private long machineVoltage = 0;
+ /**
+ * Amperage of the machine
+ */
+ private long machineAmperage = 1;
+ /**
+ * Duration of the recipe
+ */
+ private int duration = 0;
+ /**
+ * The parallel the machine has when trying to overclock
+ */
+ private int parallel = 1;
+
+ /**
+ * The min heat required for the recipe
+ */
+ private int recipeHeat = 0;
+ /**
+ * The heat the machine has when starting the recipe
+ */
+ private int machineHeat = 0;
+ /**
+ * How much the duration should be divided by for each 1800K above recipe heat
+ */
+ private double durationDecreasePerHeatOC = 4;
+ /**
+ * Whether to enable overclocking with heat like the EBF every 1800 heat difference
+ */
+ private boolean heatOC;
+ /**
+ * Whether to enable heat discounts every 900 heat difference
+ */
+ private boolean heatDiscount;
+ /**
+ * The value used for discount final eut per 900 heat
+ */
+ private double heatDiscountExponent = 0.95;
+
+ /**
+ * Discount for EUt at the beginning of calculating overclocks, like GT++ machines
+ */
+ private double eutDiscount = 1;
+ /**
+ * Speeding/Slowing up/down the duration of a recipe at the beginning of calculating overclocks, like GT++ machines
+ */
+ private double speedBoost = 1;
+
+ /**
+ * How much the energy would be multiplied by per overclock available
+ */
+ private double eutIncreasePerOC = 4;
+ /**
+ * How much the duration would be divided by per overclock made that isn't an overclock from HEAT
+ */
+ private double durationDecreasePerOC = 2;
+ /**
+ * Whether to give EUt Discount when the duration goes below one tick
+ */
+ private boolean oneTickDiscount;
+ /**
+ * Whether the multi should use amperage to overclock with an exponent. Incompatible with amperageOC
+ */
+ private boolean laserOC;
+ /**
+ * Laser OC's penalty for using high amp lasers for overclocking. Like what the Adv. Assline is doing
+ */
+ private double laserOCPenalty = 0.3;
+ /**
+ * Whether the multi should use amperage to overclock normally. Incompatible with laserOC
+ */
+ private boolean amperageOC;
+ /**
+ * If the OC calculator should only do a given amount of overclocks. Mainly used in fusion reactors
+ */
+ private boolean limitOverclocks;
+ /**
+ * Maximum amount of overclocks to perform, when limitOverclocks = true
+ */
+ private int maxOverclocks;
+ /**
+ * How many overclocks have been performed
+ */
+ private int overclockCount;
+ /**
+ * How many overclocks were performed with heat out of the overclocks we had
+ */
+ private int heatOverclockCount;
+ /**
+ * A supplier, which is used for machines which have a custom way of calculating duration, like Neutron Activator
+ */
+ private Supplier<Double> durationUnderOneTickSupplier;
+ /**
+ * Should we actually try to calculate overclocking
+ */
+ private boolean noOverclock;
+ /**
+ * variable to check whether the overclocks have been calculated
+ */
+ private boolean calculated;
+
+ private static final int HEAT_DISCOUNT_THRESHOLD = 900;
+ private static final int HEAT_PERFECT_OVERCLOCK_THRESHOLD = 1800;
+
+ /**
+ * Creates calculator that doesn't do OC at all. Will use recipe duration.
+ */
+ public static GT_OverclockCalculator ofNoOverclock(@Nonnull GT_Recipe recipe) {
+ return ofNoOverclock(recipe.mEUt, recipe.mDuration);
+ }
+
+ /**
+ * Creates calculator that doesn't do OC at all, with set duration.
+ */
+ public static GT_OverclockCalculator ofNoOverclock(long eut, int duration) {
+ return new GT_OverclockCalculator().setRecipeEUt(eut)
+ .setDuration(duration)
+ .setEUt(eut)
+ .setNoOverclock(true);
+ }
+
+ /**
+ * An Overclock helper for calculating overclocks in many different situations
+ */
+ public GT_OverclockCalculator() {}
+
+ /**
+ * @param recipeEUt Sets the Recipe's starting voltage
+ */
+ @Nonnull
+ public GT_OverclockCalculator setRecipeEUt(long recipeEUt) {
+ this.recipeVoltage = recipeEUt;
+ return this;
+ }
+
+ /**
+ * @param machineVoltage Sets the EUt that the machine can use. This is the voltage of the machine
+ */
+ @Nonnull
+ public GT_OverclockCalculator setEUt(long machineVoltage) {
+ this.machineVoltage = machineVoltage;
+ return this;
+ }
+
+ /**
+ * @param duration Sets the duration of the recipe
+ */
+ @Nonnull
+ public GT_OverclockCalculator setDuration(int duration) {
+ this.duration = duration;
+ return this;
+ }
+
+ /**
+ * @param machineAmperage Sets the Amperage that the machine can support
+ */
+ @Nonnull
+ public GT_OverclockCalculator setAmperage(long machineAmperage) {
+ this.machineAmperage = machineAmperage;
+ return this;
+ }
+
+ /**
+ * @param recipeAmperage Sets the Amperage of the recipe
+ */
+ @Nonnull
+ public GT_OverclockCalculator setRecipeAmperage(long recipeAmperage) {
+ this.recipeAmperage = recipeAmperage;
+ return this;
+ }
+
+ /**
+ * Enables Perfect OC in calculation
+ */
+ @Nonnull
+ public GT_OverclockCalculator enablePerfectOC() {
+ this.durationDecreasePerOC = 4;
+ return this;
+ }
+
+ /**
+ * Set if we should be calculating overclocking using EBF's perfectOC
+ */
+ @Nonnull
+ public GT_OverclockCalculator setHeatOC(boolean heatOC) {
+ this.heatOC = heatOC;
+ return this;
+ }
+
+ /**
+ * Sets if we should add a heat discount at the end of calculating an overclock, just like the EBF
+ */
+ @Nonnull
+ public GT_OverclockCalculator setHeatDiscount(boolean heatDiscount) {
+ this.heatDiscount = heatDiscount;
+ return this;
+ }
+
+ /**
+ * Sets the starting heat of the recipe
+ */
+ @Nonnull
+ public GT_OverclockCalculator setRecipeHeat(int recipeHeat) {
+ this.recipeHeat = recipeHeat;
+ return this;
+ }
+
+ /**
+ * Sets the heat of the coils on the machine
+ */
+ @Nonnull
+ public GT_OverclockCalculator setMachineHeat(int machineHeat) {
+ this.machineHeat = machineHeat;
+ return this;
+ }
+
+ /**
+ * Sets an EUtDiscount. 0.9 is 10% less energy. 1.1 is 10% more energy
+ */
+ @Nonnull
+ public GT_OverclockCalculator setEUtDiscount(float aEUtDiscount) {
+ this.eutDiscount = aEUtDiscount;
+ return this;
+ }
+
+ /**
+ * Sets a Speed Boost for the multiblock. 0.9 is 10% faster. 1.1 is 10% slower
+ */
+ @Nonnull
+ public GT_OverclockCalculator setSpeedBoost(float aSpeedBoost) {
+ this.speedBoost = aSpeedBoost;
+ return this;
+ }
+
+ /**
+ * Sets the parallel that the multiblock uses
+ */
+ @Nonnull
+ public GT_OverclockCalculator setParallel(int aParallel) {
+ this.parallel = aParallel;
+ return this;
+ }
+
+ /**
+ * Sets the heat discount during OC calculation if HeatOC is used. Default: 0.95 = 5% discount Used like a EU/t
+ * Discount
+ */
+ @Nonnull
+ public GT_OverclockCalculator setHeatDiscountMultiplier(float heatDiscountExponent) {
+ this.heatDiscountExponent = heatDiscountExponent;
+ return this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #setHeatPerfectOC(double)}. Calls {@link #setHeatPerfectOC(double)}
+ * where the given value is 2^heatPerfectOC
+ */
+ @Deprecated
+ @Nonnull
+ public GT_OverclockCalculator setHeatPerfectOC(int heatPerfectOC) {
+ return setHeatPerfectOC(Math.pow(2, heatPerfectOC));
+ }
+
+ /**
+ * Sets the Overclock that should be calculated when a heat OC is applied.
+ */
+ @Nonnull
+ public GT_OverclockCalculator setHeatPerfectOC(double heatPerfectOC) {
+ if (heatPerfectOC <= 0) throw new IllegalArgumentException("Heat OC can't be a negative number or zero");
+ this.durationDecreasePerHeatOC = heatPerfectOC;
+ return this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #setEUtIncreasePerOC(double)}. Calls
+ * {@link #setEUtIncreasePerOC(double)} where the given value is 2^eutIncreasePerOC
+ */
+ @Deprecated
+ @Nonnull
+ public GT_OverclockCalculator setEUtIncreasePerOC(int eutIncreasePerOC) {
+ return setEUtIncreasePerOC(Math.pow(2, eutIncreasePerOC));
+ }
+
+ /**
+ * Sets the amount that the eut would be multiplied by per overclock. Do not set as 1(ONE) if the duration decrease
+ * is also 1(ONE)!
+ */
+ @Nonnull
+ public GT_OverclockCalculator setEUtIncreasePerOC(double eutIncreasePerOC) {
+ if (eutIncreasePerOC <= 0)
+ throw new IllegalArgumentException("EUt increase can't be a negative number or zero");
+ this.eutIncreasePerOC = eutIncreasePerOC;
+ return this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #setDurationDecreasePerOC(double)}. Calls
+ * {@link #setDurationDecreasePerOC(double)} where the given value is 2^durationDecreasePerOC
+ */
+ @Deprecated
+ @Nonnull
+ public GT_OverclockCalculator setDurationDecreasePerOC(int durationDecreasePerOC) {
+ return setDurationDecreasePerOC(Math.pow(2, durationDecreasePerOC));
+ }
+
+ /**
+ * Sets the amount that the duration would be divided by per overclock. Do not set as 1(ONE) if the eut increase is
+ * also 1(ONE)!
+ */
+ @Nonnull
+ public GT_OverclockCalculator setDurationDecreasePerOC(double durationDecreasePerOC) {
+ if (durationDecreasePerOC <= 0)
+ throw new IllegalArgumentException("Duration decrease can't be a negative number or zero");
+ this.durationDecreasePerOC = durationDecreasePerOC;
+ return this;
+ }
+
+ /**
+ * Set One Tick Discount on EUt based on Duration Decrease Per Overclock. This functions the same as single blocks.
+ */
+ @Nonnull
+ public GT_OverclockCalculator setOneTickDiscount(boolean oneTickDiscount) {
+ this.oneTickDiscount = oneTickDiscount;
+ return this;
+ }
+
+ /**
+ * Limit the amount of overclocks that can be performed, regardless of how much power is available. Mainly used for
+ * fusion reactors.
+ */
+ @Nonnull
+ public GT_OverclockCalculator limitOverclockCount(int maxOverclocks) {
+ this.limitOverclocks = true;
+ this.maxOverclocks = maxOverclocks;
+ return this;
+ }
+
+ @Nonnull
+ public GT_OverclockCalculator setLaserOC(boolean laserOC) {
+ this.laserOC = laserOC;
+ return this;
+ }
+
+ @Nonnull
+ public GT_OverclockCalculator setAmperageOC(boolean amperageOC) {
+ this.amperageOC = amperageOC;
+ return this;
+ }
+
+ @Nonnull
+ public GT_OverclockCalculator setLaserOCPenalty(double laserOCPenalty) {
+ this.laserOCPenalty = laserOCPenalty;
+ return this;
+ }
+
+ /**
+ * Set a supplier for calculating custom duration for when its needed under one tick
+ */
+ @Nonnull
+ public GT_OverclockCalculator setDurationUnderOneTickSupplier(Supplier<Double> supplier) {
+ this.durationUnderOneTickSupplier = supplier;
+ return this;
+ }
+
+ /**
+ * Sets if we should do overclocking or not
+ */
+ @Nonnull
+ public GT_OverclockCalculator setNoOverclock(boolean noOverclock) {
+ this.noOverclock = noOverclock;
+ return this;
+ }
+
+ /**
+ * Call this when all values have been put it.
+ */
+ @Nonnull
+ public GT_OverclockCalculator calculate() {
+ if (calculated) {
+ throw new IllegalStateException("Tried to calculate overclocks twice");
+ }
+ calculateOverclock();
+ calculated = true;
+ return this;
+ }
+
+ private void calculateOverclock() {
+ duration = (int) Math.ceil(duration * speedBoost);
+ if (noOverclock) {
+ recipeVoltage = calculateFinalRecipeEUt(calculateHeatDiscountMultiplier());
+ return;
+ }
+ if (laserOC && amperageOC) {
+ throw new IllegalStateException("Tried to calculate overclock with both laser and amperage overclocking");
+ }
+ double heatDiscountMultiplier = calculateHeatDiscountMultiplier();
+ if (heatOC) {
+ heatOverclockCount = calculateAmountOfHeatOverclocks();
+ }
+
+ double recipePowerTier = calculateRecipePowerTier(heatDiscountMultiplier);
+ double machinePowerTier = calculateMachinePowerTier();
+
+ overclockCount = calculateAmountOfNeededOverclocks(machinePowerTier, recipePowerTier);
+ if (!amperageOC) {
+ overclockCount = Math.min(overclockCount, calculateRecipeToMachineVoltageDifference());
+ }
+
+ // Not just a safeguard. This also means that you can run a 1.2A recipe on a single hatch for a regular gt
+ // multi.
+ // This is intended, including the fact that you don't get an OC with a one tier upgrade in that case.
+ overclockCount = Math.max(overclockCount, 0);
+
+ overclockCount = limitOverclocks ? Math.min(maxOverclocks, overclockCount) : overclockCount;
+ heatOverclockCount = Math.min(heatOverclockCount, overclockCount);
+ recipeVoltage = (long) Math.floor(recipeVoltage * Math.pow(eutIncreasePerOC, overclockCount));
+ duration = (int) Math.floor(duration / Math.pow(durationDecreasePerOC, overclockCount - heatOverclockCount));
+ duration = (int) Math.floor(duration / Math.pow(durationDecreasePerHeatOC, heatOverclockCount));
+ if (oneTickDiscount) {
+ recipeVoltage = (long) Math.floor(
+ recipeVoltage
+ / Math.pow(durationDecreasePerOC, ((int) (machinePowerTier - recipePowerTier - overclockCount))));
+ if (recipeVoltage < 1) {
+ recipeVoltage = 1;
+ }
+ }
+
+ if (laserOC) {
+ calculateLaserOC();
+ }
+
+ if (duration < 1) {
+ duration = 1;
+ }
+
+ recipeVoltage = calculateFinalRecipeEUt(heatDiscountMultiplier);
+ }
+
+ private double calculateRecipePowerTier(double heatDiscountMultiplier) {
+ return calculatePowerTier(recipeVoltage * parallel * eutDiscount * heatDiscountMultiplier * recipeAmperage);
+ }
+
+ private double calculateMachinePowerTier() {
+ return calculatePowerTier(
+ machineVoltage * (amperageOC ? machineAmperage : Math.min(machineAmperage, parallel)));
+ }
+
+ private int calculateRecipeToMachineVoltageDifference() {
+ return (int) (Math.ceil(calculatePowerTier(machineVoltage)) - Math.ceil(calculatePowerTier(recipeVoltage)));
+ }
+
+ private double calculatePowerTier(double voltage) {
+ return 1 + Math.max(0, (Math.log(voltage) / LOG2) - 5) / 2;
+ }
+
+ private long calculateFinalRecipeEUt(double heatDiscountMultiplier) {
+ return (long) Math.ceil(recipeVoltage * eutDiscount * heatDiscountMultiplier * parallel * recipeAmperage);
+ }
+
+ private int calculateAmountOfHeatOverclocks() {
+ return Math.min(
+ (machineHeat - recipeHeat) / HEAT_PERFECT_OVERCLOCK_THRESHOLD,
+ calculateAmountOfOverclocks(
+ calculateMachinePowerTier(),
+ calculateRecipePowerTier(calculateHeatDiscountMultiplier())));
+ }
+
+ /**
+ * Calculate maximum possible overclocks ignoring if we are going to go under 1 tick
+ */
+ private int calculateAmountOfOverclocks(double machinePowerTier, double recipePowerTier) {
+ return (int) (machinePowerTier - recipePowerTier);
+ }
+
+ /**
+ * Calculates the amount of overclocks needed to reach 1 ticking
+ *
+ * Here we limit "the tier difference overclock" amount to a number of overclocks needed to reach 1 tick duration,
+ * for example:
+ *
+ * recipe initial duration = 250 ticks (12,5 seconds LV(1))
+ * we have LCR with IV(5) energy hatch, which overclocks at 4/4 rate
+ *
+ * log_4 (250) ~ 3,98 is the number of overclocks needed to reach 1 tick
+ *
+ * to calculate log_a(b) we can use the log property:
+ * log_a(b) = log_c(b) / log_c(a)
+ * in our case we use natural log base as 'c'
+ *
+ * as a final step we apply Math.ceil(),
+ * otherwise for fractional nums like 3,98 we will never reach 1 tick
+ */
+ private int calculateAmountOfNeededOverclocks(double machinePowerTier, double recipePowerTier) {
+ return (int) Math.min(
+ calculateAmountOfOverclocks(machinePowerTier, recipePowerTier),
+ Math.ceil(Math.log(duration) / Math.log(durationDecreasePerOC)));
+ }
+
+ private double calculateHeatDiscountMultiplier() {
+ int heatDiscounts = heatDiscount ? (machineHeat - recipeHeat) / HEAT_DISCOUNT_THRESHOLD : 0;
+ return Math.pow(heatDiscountExponent, heatDiscounts);
+ }
+
+ private void calculateLaserOC() {
+ long inputEut = machineVoltage * machineAmperage;
+ double currentPenalty = eutIncreasePerOC + laserOCPenalty;
+ while (inputEut > recipeVoltage * currentPenalty && recipeVoltage * currentPenalty > 0 && duration > 1) {
+ duration /= durationDecreasePerOC;
+ recipeVoltage *= currentPenalty;
+ currentPenalty += laserOCPenalty;
+ }
+ }
+
+ /**
+ * @return The consumption after overclock has been calculated
+ */
+ public long getConsumption() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get consumption before calculating");
+ }
+ return recipeVoltage;
+ }
+
+ /**
+ * @return The duration of the recipe after overclock has been calculated
+ */
+ public int getDuration() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get duration before calculating");
+ }
+ return duration;
+ }
+
+ /**
+ * @return Number of performed overclocks
+ */
+ public int getPerformedOverclocks() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get performed overclocks before calculating");
+ }
+ return overclockCount;
+ }
+
+ /**
+ * Returns duration as a double to show how much it is overclocking too much to determine extra parallel. This
+ * doesn't count as calculating
+ */
+ public double calculateDurationUnderOneTick() {
+ if (durationUnderOneTickSupplier != null) return durationUnderOneTickSupplier.get();
+ if (noOverclock) return duration;
+ int normalOverclocks = calculateAmountOfOverclocks(
+ calculateMachinePowerTier(),
+ calculateRecipePowerTier(calculateHeatDiscountMultiplier()));
+ normalOverclocks = limitOverclocks ? Math.min(normalOverclocks, maxOverclocks) : normalOverclocks;
+ int heatOverclocks = Math.min(calculateAmountOfHeatOverclocks(), normalOverclocks);
+ return (duration * speedBoost) / (Math.pow(durationDecreasePerOC, normalOverclocks - heatOverclocks)
+ * Math.pow(durationDecreasePerHeatOC, heatOverclocks));
+ }
+
+ /**
+ * Returns the EUt consumption one would get from overclocking under 1 tick
+ * This Doesn't count as calculating
+ *
+ * @param originalMaxParallel Parallels which are of the actual machine before the overclocking extra ones
+ */
+ public long calculateEUtConsumptionUnderOneTick(int originalMaxParallel, int currentParallel) {
+ if (noOverclock) return recipeVoltage;
+ double heatDiscountMultiplier = calculateHeatDiscountMultiplier();
+ // So what we need to do here is as follows:
+ // - First we need to figure out what out parallel multiplier for getting to that OC was
+ // - Second we need to find how many of those were from heat overclocks
+ // - Third we need to find how many were from normal overclocking.
+ // = For that we need to find how much better heat overclocks are compared to normal ones
+ // = Then remove that many from our normal overclocks
+ // - Fourth we find how many total overclocks we have
+ // - Fifth we find how many of those are needed to one tick
+ // - Finally we calculate the formula
+ // = The energy increase from our overclocks for parallel
+ // = The energy increase from our overclock to reach maximum under one tick potential
+ // =- NOTE: This will always cause machine to use full power no matter what. Otherwise it creates many
+ // anomalies.
+ // = Everything else for recipe voltage is also calculated here.
+
+ double parallelMultiplierFromOverclocks = (double) currentParallel / originalMaxParallel;
+ double amountOfParallelHeatOverclocks = Math.min(
+ Math.log(parallelMultiplierFromOverclocks) / Math.log(durationDecreasePerHeatOC),
+ calculateAmountOfHeatOverclocks());
+ double amountOfParallelOverclocks = Math.log(parallelMultiplierFromOverclocks) / Math.log(durationDecreasePerOC)
+ - amountOfParallelHeatOverclocks * (durationDecreasePerHeatOC - durationDecreasePerOC);
+ double machineTier = calculateMachinePowerTier();
+ double recipeTier = calculateRecipePowerTier(heatDiscountMultiplier);
+ double amountOfTotalOverclocks = calculateAmountOfOverclocks(machineTier, recipeTier);
+ if (recipeVoltage <= GT_Values.V[0]) {
+ amountOfTotalOverclocks = Math.min(amountOfTotalOverclocks, calculateRecipeToMachineVoltageDifference());
+ }
+ amountOfTotalOverclocks = limitOverclocks ? Math.min(amountOfTotalOverclocks, maxOverclocks)
+ : amountOfTotalOverclocks;
+ return (long) Math.ceil(
+ recipeVoltage * Math.pow(eutIncreasePerOC, amountOfParallelOverclocks + amountOfParallelHeatOverclocks)
+ * Math.pow(
+ eutIncreasePerOC,
+ amountOfTotalOverclocks - (amountOfParallelOverclocks + amountOfParallelHeatOverclocks))
+ * originalMaxParallel
+ * eutDiscount
+ * recipeAmperage
+ * heatDiscountMultiplier);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_PCBFactoryManager.java b/src/main/java/gregtech/api/util/GT_PCBFactoryManager.java
new file mode 100644
index 0000000000..990e9bd174
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_PCBFactoryManager.java
@@ -0,0 +1,25 @@
+package gregtech.api.util;
+
+import com.google.common.collect.HashBiMap;
+
+import gregtech.api.enums.Materials;
+
+public class GT_PCBFactoryManager {
+
+ private static final HashBiMap<Materials, Integer> mPlasticTiers = HashBiMap.create();
+ public static int mTiersOfPlastics = 0;
+
+ public static void addPlasticTier(Materials aMaterial, int aTier) {
+ mPlasticTiers.put(aMaterial, aTier);
+ mTiersOfPlastics++;
+ }
+
+ public static int getPlasticTier(Materials aMaterial) {
+ return mPlasticTiers.get(aMaterial);
+ }
+
+ public static Materials getPlasticMaterialFromTier(int aTier) {
+ return mPlasticTiers.inverse()
+ .get(aTier);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ParallelHelper.java b/src/main/java/gregtech/api/util/GT_ParallelHelper.java
new file mode 100644
index 0000000000..141ea35e9e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ParallelHelper.java
@@ -0,0 +1,717 @@
+package gregtech.api.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.interfaces.tileentity.IRecipeLockable;
+import gregtech.api.interfaces.tileentity.IVoidable;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.objects.XSTR;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.recipe.check.SingleRecipeCheck;
+
+@SuppressWarnings({ "unused", "UnusedReturnValue" })
+public class GT_ParallelHelper {
+
+ private static final double MAX_BATCH_MODE_TICK_TIME = 128;
+ /**
+ * Machine used for calculation
+ */
+ private IVoidable machine;
+ /**
+ * Machine used for single recipe locking calculation
+ */
+ private IRecipeLockable singleRecipeMachine;
+ /**
+ * Is locked to a single recipe?
+ */
+ private boolean isRecipeLocked;
+ /**
+ * Recipe used when trying to calculate parallels
+ */
+ private GT_Recipe recipe;
+ /**
+ * EUt available to the multiblock (This should be the total eut available)
+ */
+ private long availableEUt;
+ /**
+ * The current parallel possible for the multiblock
+ */
+ private int currentParallel = 0;
+ /**
+ * The maximum possible parallel possible for the multiblock
+ */
+ private int maxParallel = 1;
+ /**
+ * The Batch Modifier applied when batch mode is enabled. 1 does nothing. 2 doubles max possible
+ * parallel, but also duration
+ */
+ private int batchModifier = 1;
+ /**
+ * The inputs of the multiblock for the current recipe check
+ */
+ private ItemStack[] itemInputs;
+ /**
+ * The inputs of the machine for current recipe check
+ */
+ private ItemInventoryLogic itemInputInventory;
+ /**
+ * The output item inventory of the machine
+ */
+ private ItemInventoryLogic itemOutputInventory;
+ /**
+ * The outputs of the recipe with the applied parallel
+ */
+ private ItemStack[] itemOutputs;
+ /**
+ * The inputs of the multiblock for the current recipe check
+ */
+ private FluidStack[] fluidInputs;
+ /**
+ * The inputs of the machine for the current recipe check
+ */
+ private FluidInventoryLogic fluidInputInventory;
+ /**
+ * The output fluid inventory of the machine;
+ */
+ private FluidInventoryLogic fluidOutputInventory;
+ /**
+ * The outputs of the recipe with the applied parallel
+ */
+ private FluidStack[] fluidOutputs;
+ /**
+ * Does the multi have void protection enabled for items
+ */
+ private boolean protectExcessItem;
+ /**
+ * Does the multi have void protection enabled for fluids
+ */
+ private boolean protectExcessFluid;
+ /**
+ * Should the Parallel Helper automatically consume for the multi
+ */
+ private boolean consume;
+ /**
+ * Is batch mode turned on?
+ */
+ private boolean batchMode;
+ /**
+ * Should the Parallel Helper automatically calculate the outputs of the recipe with current parallel?
+ */
+ private boolean calculateOutputs;
+ /**
+ * Has the Parallel Helper been built?
+ */
+ private boolean built;
+ /**
+ * What is the duration multiplier with batch mode enabled
+ */
+ private double durationMultiplier;
+ /**
+ * Modifier which is applied on the recipe eut. Useful for GT++ machines
+ */
+ private float eutModifier = 1;
+ /**
+ * Multiplier that is applied on the output chances
+ */
+ private double chanceMultiplier = 1;
+ /**
+ * Multiplier by which the output will be multiplied
+ */
+ private int outputMultiplier = 1;
+ /**
+ * Method for calculating max parallel from given inputs.
+ */
+ private MaxParallelCalculator maxParallelCalculator = GT_Recipe::maxParallelCalculatedByInputs;
+ /**
+ * Method for consuming inputs after determining how many parallels it can execute.
+ */
+ private InputConsumer inputConsumer = GT_Recipe::consumeInput;
+
+ /**
+ * Calculator to use for overclocking
+ */
+ private GT_OverclockCalculator calculator;
+ @Nonnull
+ private CheckRecipeResult result = CheckRecipeResultRegistry.NONE;
+
+ private Function<Integer, ItemStack[]> customItemOutputCalculation;
+
+ private Function<Integer, FluidStack[]> customFluidOutputCalculation;
+
+ /**
+ * MuTE Mode this is a mode for changing how the GT_ParallelHelper works as Mutes don't use ItemStack and FluidStack
+ * arrays for inputs
+ */
+ private boolean muteMode = false;
+
+ public GT_ParallelHelper() {}
+
+ /**
+ * Sets machine, with current configuration for void protection mode.
+ */
+ @Nonnull
+ public GT_ParallelHelper setMachine(IVoidable machine) {
+ return setMachine(machine, machine.protectsExcessItem(), machine.protectsExcessFluid());
+ }
+
+ /**
+ * Sets machine, with void protection mode forcibly.
+ */
+ @Nonnull
+ public GT_ParallelHelper setMachine(IVoidable machine, boolean protectExcessItem, boolean protectExcessFluid) {
+ this.protectExcessItem = protectExcessItem;
+ this.protectExcessFluid = protectExcessFluid;
+ this.machine = machine;
+ return this;
+ }
+
+ /**
+ * Sets the recipe, which will be used for the parallel calculation
+ */
+ @Nonnull
+ public GT_ParallelHelper setRecipe(@Nonnull GT_Recipe aRecipe) {
+ recipe = Objects.requireNonNull(aRecipe);
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setRecipeLocked(IRecipeLockable singleRecipeMachine, boolean isRecipeLocked) {
+ this.singleRecipeMachine = singleRecipeMachine;
+ this.isRecipeLocked = isRecipeLocked;
+ return this;
+ }
+
+ /**
+ * Sets the items available for the recipe check
+ */
+ @Nonnull
+ public GT_ParallelHelper setItemInputs(ItemStack... aItemInputs) {
+ this.itemInputs = aItemInputs;
+ return this;
+ }
+
+ /**
+ * Sets the fluid inputs available for the recipe check
+ */
+ @Nonnull
+ public GT_ParallelHelper setFluidInputs(FluidStack... aFluidInputs) {
+ this.fluidInputs = aFluidInputs;
+ return this;
+ }
+
+ /**
+ * Sets the available eut when trying for more parallels
+ */
+ @Nonnull
+ public GT_ParallelHelper setAvailableEUt(long aAvailableEUt) {
+ this.availableEUt = aAvailableEUt;
+ return this;
+ }
+
+ /**
+ * Sets the modifier for recipe eut. 1 does nothing 0.9 is 10% less. 1.1 is 10% more
+ */
+ @Nonnull
+ public GT_ParallelHelper setEUtModifier(float aEUtModifier) {
+ this.eutModifier = aEUtModifier;
+ return this;
+ }
+
+ /**
+ * Sets the multiplier that is applied on output chances. 1 does nothing. 0.9 is 10% less. 1.1 is 10% more.
+ * Only useful for item outputs for sure.
+ */
+ @Nonnull
+ public GT_ParallelHelper setChanceMultiplier(double chanceMultiplier) {
+ this.chanceMultiplier = chanceMultiplier;
+ return this;
+ }
+
+ /**
+ * Sets the item/fluid output multiplier. 1 does nothing. 2 doubles the item and fluid outputs.
+ */
+ @Nonnull
+ public GT_ParallelHelper setOutputMultiplier(int outputMultiplier) {
+ this.outputMultiplier = outputMultiplier;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setCalculator(GT_OverclockCalculator calculator) {
+ this.calculator = calculator;
+ return this;
+ }
+
+ /**
+ * Set if we should consume inputs or not when trying for parallels
+ *
+ * @param consume Should we consume inputs
+ */
+ @Nonnull
+ public GT_ParallelHelper setConsumption(boolean consume) {
+ this.consume = consume;
+ return this;
+ }
+
+ /**
+ * Sets the MaxParallel a multi can handle
+ */
+ @Nonnull
+ public GT_ParallelHelper setMaxParallel(int maxParallel) {
+ this.maxParallel = maxParallel;
+ return this;
+ }
+
+ /**
+ * Enables Batch mode. Can do up to an additional processed recipes of mCurrentParallel * mBatchModifier A batch
+ * modifier of 1 does nothing
+ */
+ @Nonnull
+ public GT_ParallelHelper enableBatchMode(int batchModifier) {
+ this.batchMode = batchModifier > 1;
+ this.batchModifier = batchModifier;
+ return this;
+ }
+
+ /**
+ * Sets if we should calculate outputs with the parallels we found or not
+ *
+ * @param calculateOutputs Should we calculate outputs with the helper or not
+ */
+ @Nonnull
+ public GT_ParallelHelper setOutputCalculation(boolean calculateOutputs) {
+ this.calculateOutputs = calculateOutputs;
+ return this;
+ }
+
+ /**
+ * Set a custom way to calculate item outputs. You are given the amount of parallels and must return an ItemStack
+ * array
+ */
+ @Nonnull
+ public GT_ParallelHelper setCustomItemOutputCalculation(Function<Integer, ItemStack[]> custom) {
+ customItemOutputCalculation = custom;
+ return this;
+ }
+
+ /**
+ * Set a custom way to calculate item outputs. You are given the amount of parallels and must return a FluidStack
+ * array
+ */
+ @Nonnull
+ public GT_ParallelHelper setCustomFluidOutputCalculation(Function<Integer, FluidStack[]> custom) {
+ customFluidOutputCalculation = custom;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setMuTEMode(boolean muteMode) {
+ this.muteMode = muteMode;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setItemInputInventory(ItemInventoryLogic itemInputInventory) {
+ this.itemInputInventory = itemInputInventory;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setFluidInputInventory(FluidInventoryLogic fluidInputInventory) {
+ this.fluidInputInventory = fluidInputInventory;
+ return this;
+ }
+
+ /**
+ * Sets method for calculating max parallel from given inputs.
+ */
+ public GT_ParallelHelper setMaxParallelCalculator(MaxParallelCalculator maxParallelCalculator) {
+ this.maxParallelCalculator = maxParallelCalculator;
+ return this;
+ }
+
+ /**
+ * Sets method for consuming inputs after determining how many parallels it can execute.
+ */
+ public GT_ParallelHelper setInputConsumer(InputConsumer inputConsumer) {
+ this.inputConsumer = inputConsumer;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setItemOutputInventory(ItemInventoryLogic itemOutputInventory) {
+ this.itemOutputInventory = itemOutputInventory;
+ return this;
+ }
+
+ @Nonnull
+ public GT_ParallelHelper setFluidOutputInventory(FluidInventoryLogic fluidOutputInventory) {
+ this.fluidOutputInventory = fluidOutputInventory;
+ return this;
+ }
+
+ /**
+ * Finishes the GT_ParallelHelper. Anything changed after this will not effect anything
+ */
+ @Nonnull
+ public GT_ParallelHelper build() {
+ if (built) {
+ throw new IllegalStateException("Tried to build twice");
+ }
+ if (recipe == null) {
+ throw new IllegalStateException("Recipe is not set");
+ }
+ built = true;
+ determineParallel();
+ return this;
+ }
+
+ /**
+ * @return The current parallels possible by the multiblock
+ */
+ public int getCurrentParallel() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get parallels before building");
+ }
+ return currentParallel;
+ }
+
+ /**
+ * @return The duration multiplier if batch mode was enabled for the multiblock
+ */
+ public double getDurationMultiplierDouble() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get duration multiplier before building");
+ }
+ if (batchMode && durationMultiplier > 0) {
+ return durationMultiplier;
+ }
+ return 1;
+ }
+
+ /**
+ * @return The ItemOutputs from the recipe
+ */
+ @Nonnull
+ public ItemStack[] getItemOutputs() {
+ if (!built || !calculateOutputs) {
+ throw new IllegalStateException(
+ "Tried to get item outputs before building or without enabling calculation of outputs");
+ }
+ return itemOutputs;
+ }
+
+ /**
+ * @return The FluidOutputs from the recipe
+ */
+ @Nonnull
+ public FluidStack[] getFluidOutputs() {
+ if (!built || !calculateOutputs) {
+ throw new IllegalStateException(
+ "Tried to get fluid outputs before building or without enabling calculation of outputs");
+ }
+ return fluidOutputs;
+ }
+
+ /**
+ * @return The result of why a recipe could've failed or succeeded
+ */
+ @Nonnull
+ public CheckRecipeResult getResult() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get recipe result before building");
+ }
+ return result;
+ }
+
+ /**
+ * Called by build(). Determines the parallels and everything else that needs to be done at build time
+ */
+ protected void determineParallel() {
+ if (maxParallel <= 0) {
+ return;
+ }
+ if (itemInputs == null) {
+ itemInputs = new ItemStack[0];
+ }
+ if (fluidInputs == null) {
+ fluidInputs = new FluidStack[0];
+ }
+
+ if (!consume) {
+ copyInputs();
+ }
+
+ if (calculator == null) {
+ calculator = new GT_OverclockCalculator().setEUt(availableEUt)
+ .setRecipeEUt(recipe.mEUt)
+ .setDuration(recipe.mDuration)
+ .setEUtDiscount(eutModifier);
+ }
+
+ final int tRecipeEUt = (int) Math.ceil(recipe.mEUt * eutModifier);
+ if (availableEUt < tRecipeEUt) {
+ result = CheckRecipeResultRegistry.insufficientPower(tRecipeEUt);
+ return;
+ }
+
+ // Save the original max parallel before calculating our overclocking under 1 tick
+ int originalMaxParallel = maxParallel;
+ double tickTimeAfterOC = calculator.setParallel(originalMaxParallel)
+ .calculateDurationUnderOneTick();
+ if (tickTimeAfterOC < 1) {
+ maxParallel = GT_Utility.safeInt((long) (maxParallel / tickTimeAfterOC), 0);
+ }
+
+ int maxParallelBeforeBatchMode = maxParallel;
+ if (batchMode) {
+ maxParallel = GT_Utility.safeInt((long) maxParallel * batchModifier, 0);
+ }
+
+ final ItemStack[] truncatedItemOutputs = recipe.mOutputs != null
+ ? Arrays.copyOfRange(recipe.mOutputs, 0, Math.min(machine.getItemOutputLimit(), recipe.mOutputs.length))
+ : new ItemStack[0];
+ final FluidStack[] truncatedFluidOutputs = recipe.mFluidOutputs != null ? Arrays
+ .copyOfRange(recipe.mFluidOutputs, 0, Math.min(machine.getFluidOutputLimit(), recipe.mFluidOutputs.length))
+ : new FluidStack[0];
+
+ SingleRecipeCheck recipeCheck = null;
+ SingleRecipeCheck.Builder tSingleRecipeCheckBuilder = null;
+ if (isRecipeLocked && singleRecipeMachine != null) {
+ recipeCheck = singleRecipeMachine.getSingleRecipeCheck();
+ if (recipeCheck == null) {
+ // Machine is configured to lock to a single recipe, but haven't built the recipe checker yet.
+ // Build the checker on next successful recipe.
+ RecipeMap<?> recipeMap = singleRecipeMachine.getRecipeMap();
+ if (recipeMap != null) {
+ tSingleRecipeCheckBuilder = SingleRecipeCheck.builder(recipeMap)
+ .setBefore(itemInputs, fluidInputs);
+ }
+ }
+ }
+
+ // Let's look at how many parallels we can get with void protection
+ if (protectExcessItem || protectExcessFluid) {
+ if (machine == null && !muteMode) {
+ throw new IllegalStateException("Tried to calculate void protection, but machine is not set");
+ }
+ VoidProtectionHelper voidProtectionHelper = new VoidProtectionHelper();
+ voidProtectionHelper.setMachine(machine)
+ .setItemOutputs(truncatedItemOutputs)
+ .setFluidOutputs(truncatedFluidOutputs)
+ .setChangeGetter(recipe::getOutputChance)
+ .setOutputMultiplier(outputMultiplier)
+ .setChanceMultiplier(chanceMultiplier)
+ .setMaxParallel(maxParallel)
+ .setItemOutputInventory(itemOutputInventory)
+ .setFluidOutputInventory(fluidOutputInventory)
+ .setMuTEMode(muteMode)
+ .build();
+ maxParallel = Math.min(voidProtectionHelper.getMaxParallel(), maxParallel);
+ if (voidProtectionHelper.isItemFull()) {
+ result = CheckRecipeResultRegistry.ITEM_OUTPUT_FULL;
+ return;
+ }
+ if (voidProtectionHelper.isFluidFull()) {
+ result = CheckRecipeResultRegistry.FLUID_OUTPUT_FULL;
+ return;
+ }
+ }
+
+ maxParallelBeforeBatchMode = Math.min(maxParallel, maxParallelBeforeBatchMode);
+
+ // determine normal parallel
+ int actualMaxParallel = tRecipeEUt > 0 ? (int) Math.min(maxParallelBeforeBatchMode, availableEUt / tRecipeEUt)
+ : maxParallelBeforeBatchMode;
+ if (recipeCheck != null) {
+ currentParallel = recipeCheck.checkRecipeInputs(true, actualMaxParallel, itemInputs, fluidInputs);
+ } else {
+ currentParallel = (int) maxParallelCalculator.calculate(recipe, actualMaxParallel, fluidInputs, itemInputs);
+ if (currentParallel > 0) {
+ if (tSingleRecipeCheckBuilder != null) {
+ // If recipe checker is not built yet, build and set it
+ inputConsumer.consume(recipe, 1, fluidInputs, itemInputs);
+ SingleRecipeCheck builtCheck = tSingleRecipeCheckBuilder.setAfter(itemInputs, fluidInputs)
+ .setRecipe(recipe)
+ .build();
+ singleRecipeMachine.setSingleRecipeCheck(builtCheck);
+ inputConsumer.consume(recipe, currentParallel - 1, fluidInputs, itemInputs);
+ } else {
+ inputConsumer.consume(recipe, currentParallel, fluidInputs, itemInputs);
+ }
+ }
+ }
+
+ if (currentParallel <= 0) {
+ result = CheckRecipeResultRegistry.INTERNAL_ERROR;
+ return;
+ }
+
+ long eutUseAfterOC = calculator.calculateEUtConsumptionUnderOneTick(originalMaxParallel, currentParallel);
+ calculator.setParallel(Math.min(currentParallel, originalMaxParallel))
+ .calculate();
+ if (currentParallel > originalMaxParallel) {
+ calculator.setRecipeEUt(eutUseAfterOC);
+ }
+ // If Batch Mode is enabled determine how many extra parallels we can get
+ if (batchMode && currentParallel > 0 && calculator.getDuration() < MAX_BATCH_MODE_TICK_TIME) {
+ int tExtraParallels;
+ double batchMultiplierMax = MAX_BATCH_MODE_TICK_TIME / calculator.getDuration();
+ final int maxExtraParallels = (int) Math.floor(
+ Math.min(
+ currentParallel * Math.min(batchMultiplierMax - 1, batchModifier - 1),
+ maxParallel - currentParallel));
+ if (recipeCheck != null) {
+ tExtraParallels = recipeCheck.checkRecipeInputs(true, maxExtraParallels, itemInputs, fluidInputs);
+ } else {
+ tExtraParallels = (int) maxParallelCalculator
+ .calculate(recipe, maxExtraParallels, fluidInputs, itemInputs);
+ inputConsumer.consume(recipe, tExtraParallels, fluidInputs, itemInputs);
+ }
+ durationMultiplier = 1.0f + (float) tExtraParallels / currentParallel;
+ currentParallel += tExtraParallels;
+ }
+
+ // If we want to calculate outputs we do it here
+ if (calculateOutputs && currentParallel > 0) {
+ calculateItemOutputs(truncatedItemOutputs);
+ calculateFluidOutputs(truncatedFluidOutputs);
+ }
+ result = CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+
+ protected void copyInputs() {
+ ItemStack[] itemInputsToUse;
+ FluidStack[] fluidInputsToUse;
+ itemInputsToUse = new ItemStack[itemInputs.length];
+ for (int i = 0; i < itemInputs.length; i++) {
+ itemInputsToUse[i] = itemInputs[i].copy();
+ }
+ fluidInputsToUse = new FluidStack[fluidInputs.length];
+ for (int i = 0; i < fluidInputs.length; i++) {
+ fluidInputsToUse[i] = fluidInputs[i].copy();
+ }
+ itemInputs = itemInputsToUse;
+ fluidInputs = fluidInputsToUse;
+ }
+
+ private void calculateItemOutputs(ItemStack[] truncatedItemOutputs) {
+ if (customItemOutputCalculation != null) {
+ itemOutputs = customItemOutputCalculation.apply(currentParallel);
+ return;
+ }
+ if (truncatedItemOutputs.length == 0) return;
+ ArrayList<ItemStack> itemOutputsList = new ArrayList<>();
+ for (int i = 0; i < truncatedItemOutputs.length; i++) {
+ if (recipe.getOutput(i) == null) continue;
+ ItemStack origin = recipe.getOutput(i)
+ .copy();
+ final long itemStackSize = origin.stackSize;
+ double chancedOutputMultiplier = calculateChancedOutputMultiplier(
+ (int) (recipe.getOutputChance(i) * chanceMultiplier),
+ currentParallel);
+ long items = (long) Math.ceil(itemStackSize * chancedOutputMultiplier * outputMultiplier);
+ addItemsLong(itemOutputsList, origin, items);
+ }
+ itemOutputs = itemOutputsList.toArray(new ItemStack[0]);
+ }
+
+ private void calculateFluidOutputs(FluidStack[] truncatedFluidOutputs) {
+ if (customFluidOutputCalculation != null) {
+ fluidOutputs = customFluidOutputCalculation.apply(currentParallel);
+ return;
+ }
+ if (truncatedFluidOutputs.length == 0) return;
+ ArrayList<FluidStack> fluidOutputsList = new ArrayList<>();
+ for (int i = 0; i < truncatedFluidOutputs.length; i++) {
+ if (recipe.getFluidOutput(i) == null) continue;
+ FluidStack origin = recipe.getFluidOutput(i)
+ .copy();
+ long fluids = (long) this.outputMultiplier * origin.amount * currentParallel;
+
+ addFluidsLong(fluidOutputsList, origin, fluids);
+ }
+ fluidOutputs = fluidOutputsList.toArray(new FluidStack[0]);
+ }
+
+ private static final Random rand = new Random();
+
+ public static double calculateChancedOutputMultiplier(int chanceInt, int parallel) {
+ // Multiply the integer part of the chance directly with parallel
+ double multiplier = Math.floorDiv(chanceInt, 10000) * parallel;
+ int transformedChanceInt = chanceInt % 10000;
+ if (transformedChanceInt == 0) return multiplier;
+ // Calculation of the Decimal Part of chance
+ double chance = transformedChanceInt / 10000.0;
+ double mean = parallel * chance;
+ double stdDev = Math.sqrt(parallel * chance * (1 - chance));
+ // Check if everything within 3 standard deviations of mean is within the range
+ // of possible values (0 ~ currentParallel)
+ boolean isSuitableForFittingWithNormalDistribution = mean - 3 * stdDev >= 0 && mean + 3 * stdDev <= parallel;
+ if (isSuitableForFittingWithNormalDistribution) {
+ // Use Normal Distribution to fit Binomial Distribution
+ double tMultiplier = stdDev * rand.nextGaussian() + mean;
+ multiplier += Math.max(Math.min(tMultiplier, parallel), 0);
+ } else {
+ // Do Binomial Distribution by loop
+ for (int roll = 0; roll < parallel; roll++) {
+ if (transformedChanceInt > XSTR.XSTR_INSTANCE.nextInt(10000)) {
+ multiplier += 1;
+ }
+ }
+ }
+ return multiplier;
+ }
+
+ public static void addItemsLong(ArrayList<ItemStack> itemList, ItemStack origin, long amount) {
+ if (amount > 0) {
+ while (amount > Integer.MAX_VALUE) {
+ ItemStack item = origin.copy();
+ item.stackSize = Integer.MAX_VALUE;
+ itemList.add(item);
+ amount -= Integer.MAX_VALUE;
+ }
+ ItemStack item = origin.copy();
+ item.stackSize = (int) amount;
+ itemList.add(item);
+ }
+ }
+
+ public static void addFluidsLong(ArrayList<FluidStack> fluidList, FluidStack origin, long amount) {
+ if (amount > 0) {
+ while (amount > Integer.MAX_VALUE) {
+ FluidStack fluid = origin.copy();
+ fluid.amount = Integer.MAX_VALUE;
+ fluidList.add(fluid);
+ amount -= Integer.MAX_VALUE;
+ }
+ FluidStack fluid = origin.copy();
+ fluid.amount = (int) amount;
+ fluidList.add(fluid);
+ }
+ }
+
+ @FunctionalInterface
+ public interface MaxParallelCalculator {
+
+ double calculate(GT_Recipe recipe, int maxParallel, FluidStack[] fluids, ItemStack[] items);
+ }
+
+ @FunctionalInterface
+ public interface InputConsumer {
+
+ void consume(GT_Recipe recipe, int amountMultiplier, FluidStack[] aFluidInputs, ItemStack[] aInputs);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_PlayedSound.java b/src/main/java/gregtech/api/util/GT_PlayedSound.java
new file mode 100644
index 0000000000..05d61e9833
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_PlayedSound.java
@@ -0,0 +1,42 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.E;
+
+import net.minecraft.util.ResourceLocation;
+
+public class GT_PlayedSound {
+
+ public final String mSoundName;
+ public final int mX, mY, mZ;
+
+ public GT_PlayedSound(ResourceLocation aSoundResourceLocation, double aX, double aY, double aZ) {
+ mSoundName = aSoundResourceLocation.toString();
+ mX = (int) aX;
+ mY = (int) aY;
+ mZ = (int) aZ;
+ }
+
+ /**
+ * @inheritDoc
+ * @deprecated Use {@link GT_PlayedSound(ResourceLocation, double, double, double)}
+ */
+ @Deprecated
+ public GT_PlayedSound(String aSoundName, double aX, double aY, double aZ) {
+ this(new ResourceLocation(aSoundName == null ? E : aSoundName), aX, aY, aZ);
+ }
+
+ @Override
+ public boolean equals(Object aObject) {
+ if (aObject instanceof GT_PlayedSound) {
+ return ((GT_PlayedSound) aObject).mX == mX && ((GT_PlayedSound) aObject).mY == mY
+ && ((GT_PlayedSound) aObject).mZ == mZ
+ && ((GT_PlayedSound) aObject).mSoundName.equals(mSoundName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mX + mY + mZ + mSoundName.hashCode();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ProcessingArray_Manager.java b/src/main/java/gregtech/api/util/GT_ProcessingArray_Manager.java
new file mode 100644
index 0000000000..ead9393d0e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ProcessingArray_Manager.java
@@ -0,0 +1,51 @@
+package gregtech.api.util;
+
+import java.util.HashMap;
+
+import net.minecraft.item.ItemStack;
+
+import gregtech.api.enums.SoundResource;
+import gregtech.api.recipe.RecipeMap;
+
+@Deprecated
+public class GT_ProcessingArray_Manager {
+
+ private static final HashMap<String, RecipeMap<?>> mRecipeSaves = new HashMap<>();
+ private static final HashMap<String, SoundResource> machineSounds = new HashMap<>();
+
+ // Adds recipe Maps to the PA using the machines unlocalized name.
+ // Example: basicmachine.electrolyzer, with its recipe map will add the electrolyzer's recipe map to the PA
+ public static void addRecipeMapToPA(String aMachineName, RecipeMap<?> aMap) {
+ if (aMachineName != null) {
+ mRecipeSaves.put(aMachineName, aMap);
+ }
+ }
+
+ // Allows the PA to extract the recipe map for the machine inside it.
+ public static RecipeMap<?> giveRecipeMap(String aMachineName) {
+ if (aMachineName != null) {
+ return mRecipeSaves.get(aMachineName);
+ }
+ return null;
+ }
+
+ public static void addSoundResourceToPA(String machineName, SoundResource soundResource) {
+ if (machineName != null) {
+ machineSounds.put(machineName, soundResource);
+ }
+ }
+
+ public static SoundResource getSoundResource(String machineName) {
+ if (machineName != null) {
+ return machineSounds.get(machineName);
+ }
+ return null;
+ }
+
+ public static String getMachineName(ItemStack stack) {
+ int length = stack.getUnlocalizedName()
+ .length();
+ return stack.getUnlocalizedName()
+ .substring(17, length - 8); // trim "gt.blockmachines." and ".tier.xx"
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Recipe.java b/src/main/java/gregtech/api/util/GT_Recipe.java
new file mode 100644
index 0000000000..04f65a8342
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Recipe.java
@@ -0,0 +1,1271 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.D2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.Contract;
+
+import cpw.mods.fml.common.Loader;
+import cpw.mods.fml.common.ModContainer;
+import gregtech.GT_Mod;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_MultiInput;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.recipe.RecipeCategory;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.RecipeMaps;
+import gregtech.api.recipe.RecipeMetadataKey;
+import gregtech.api.recipe.metadata.EmptyRecipeMetadataStorage;
+import gregtech.api.recipe.metadata.IRecipeMetadataStorage;
+import gregtech.api.util.extensions.ArrayExt;
+import gregtech.api.util.item.ItemHolder;
+import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_InputBus_ME;
+import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_Input_ME;
+import ic2.core.Ic2Items;
+import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2LongMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
+
+public class GT_Recipe implements Comparable<GT_Recipe> {
+
+ /**
+ * If you want to change the Output, feel free to modify or even replace the whole ItemStack Array, for Inputs,
+ * please add a new Recipe, because of the HashMaps.
+ */
+ public ItemStack[] mInputs, mOutputs;
+ /**
+ * If you want to change the Output, feel free to modify or even replace the whole ItemStack Array, for Inputs,
+ * please add a new Recipe, because of the HashMaps.
+ */
+ public FluidStack[] mFluidInputs, mFluidOutputs;
+ /**
+ * If you changed the amount of Array-Items inside the Output Array then the length of this Array must be larger or
+ * equal to the Output Array. A chance of 10000 equals 100%
+ */
+ public int[] mChances;
+ /**
+ * An Item that needs to be inside the Special Slot, like for example the Copy Slot inside the Printer. This is only
+ * useful for Fake Recipes in NEI, since findRecipe() and containsInput() don't give a shit about this Field. Lists
+ * are also possible.
+ */
+ public Object mSpecialItems;
+
+ public int mDuration, mEUt, mSpecialValue;
+ /**
+ * Use this to just disable a specific Recipe, but the Configuration enables that already for every single Recipe.
+ */
+ public boolean mEnabled = true;
+ /**
+ * If this Recipe is hidden from NEI
+ */
+ public boolean mHidden = false;
+ /**
+ * If this Recipe is Fake and therefore doesn't get found by the findRecipe Function (It is still in the HashMaps,
+ * so that containsInput does return T on those fake Inputs)
+ */
+ public boolean mFakeRecipe = false;
+ /**
+ * If this Recipe can be stored inside a Machine in order to make Recipe searching more Efficient by trying the
+ * previously used Recipe first. In case you have a Recipe Map overriding things and returning one time use Recipes,
+ * you have to set this to F.
+ */
+ public boolean mCanBeBuffered = true;
+ /**
+ * If this Recipe needs the Output Slots to be completely empty. Needed in case you have randomised Outputs
+ */
+ public boolean mNeedsEmptyOutput = false;
+ /**
+ * If this is set to true, NBT equality is required for recipe check.
+ */
+ public boolean isNBTSensitive = false;
+ /**
+ * Used for describing recipes that do not fit the default recipe pattern (for example Large Boiler Fuels)
+ */
+ private String[] neiDesc = null;
+ /**
+ * Holds a set of metadata for this recipe.
+ */
+ @Nonnull
+ private final IRecipeMetadataStorage metadataStorage;
+ /**
+ * Category this recipe belongs to. Recipes belonging to recipemap are forced to have non-null category when added,
+ * otherwise it can be null.
+ */
+ private RecipeCategory recipeCategory;
+ /**
+ * Stores which mod added this recipe
+ */
+ public List<ModContainer> owners = new ArrayList<>();
+ /**
+ * Stores stack traces where this recipe was added
+ */
+ // BW wants to overwrite it, so no final
+ public List<List<String>> stackTraces = new ArrayList<>();
+
+ private GT_Recipe(GT_Recipe aRecipe, boolean shallow) {
+ mInputs = shallow ? aRecipe.mInputs : GT_Utility.copyItemArray(aRecipe.mInputs);
+ mOutputs = shallow ? aRecipe.mOutputs : GT_Utility.copyItemArray(aRecipe.mOutputs);
+ mSpecialItems = aRecipe.mSpecialItems;
+ mChances = aRecipe.mChances;
+ mFluidInputs = shallow ? aRecipe.mFluidInputs : GT_Utility.copyFluidArray(aRecipe.mFluidInputs);
+ mFluidOutputs = shallow ? aRecipe.mFluidOutputs : GT_Utility.copyFluidArray(aRecipe.mFluidOutputs);
+ mDuration = aRecipe.mDuration;
+ mSpecialValue = aRecipe.mSpecialValue;
+ mEUt = aRecipe.mEUt;
+ mNeedsEmptyOutput = aRecipe.mNeedsEmptyOutput;
+ isNBTSensitive = aRecipe.isNBTSensitive;
+ mCanBeBuffered = aRecipe.mCanBeBuffered;
+ mFakeRecipe = aRecipe.mFakeRecipe;
+ mEnabled = aRecipe.mEnabled;
+ mHidden = aRecipe.mHidden;
+ metadataStorage = EmptyRecipeMetadataStorage.INSTANCE;
+ owners = new ArrayList<>(aRecipe.owners);
+ reloadOwner();
+ }
+
+ /**
+ * Only for {@link GT_RecipeBuilder}.
+ */
+ GT_Recipe(ItemStack[] mInputs, ItemStack[] mOutputs, FluidStack[] mFluidInputs, FluidStack[] mFluidOutputs,
+ int[] mChances, Object mSpecialItems, int mDuration, int mEUt, int mSpecialValue, boolean mEnabled,
+ boolean mHidden, boolean mFakeRecipe, boolean mCanBeBuffered, boolean mNeedsEmptyOutput, boolean nbtSensitive,
+ String[] neiDesc, @Nullable IRecipeMetadataStorage metadataStorage, RecipeCategory recipeCategory) {
+ this.mInputs = mInputs;
+ this.mOutputs = mOutputs;
+ this.mFluidInputs = mFluidInputs;
+ this.mFluidOutputs = mFluidOutputs;
+ this.mChances = mChances;
+ this.mSpecialItems = mSpecialItems;
+ this.mDuration = mDuration;
+ this.mEUt = mEUt;
+ this.mSpecialValue = mSpecialValue;
+ this.mEnabled = mEnabled;
+ this.mHidden = mHidden;
+ this.mFakeRecipe = mFakeRecipe;
+ this.mCanBeBuffered = mCanBeBuffered;
+ this.mNeedsEmptyOutput = mNeedsEmptyOutput;
+ this.isNBTSensitive = nbtSensitive;
+ this.neiDesc = neiDesc;
+ this.metadataStorage = metadataStorage == null ? EmptyRecipeMetadataStorage.INSTANCE : metadataStorage.copy();
+ this.recipeCategory = recipeCategory;
+
+ reloadOwner();
+ }
+
+ public GT_Recipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems, int[] aChances,
+ FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
+ if (aInputs == null) aInputs = new ItemStack[0];
+ if (aOutputs == null) aOutputs = new ItemStack[0];
+ if (aFluidInputs == null) aFluidInputs = new FluidStack[0];
+ if (aFluidOutputs == null) aFluidOutputs = new FluidStack[0];
+ if (aChances == null) aChances = new int[aOutputs.length];
+ if (aChances.length < aOutputs.length) aChances = Arrays.copyOf(aChances, aOutputs.length);
+
+ aInputs = ArrayExt.withoutTrailingNulls(aInputs, ItemStack[]::new);
+ aOutputs = ArrayExt.withoutTrailingNulls(aOutputs, ItemStack[]::new);
+ aFluidInputs = ArrayExt.withoutNulls(aFluidInputs, FluidStack[]::new);
+ aFluidOutputs = ArrayExt.withoutNulls(aFluidOutputs, FluidStack[]::new);
+
+ GT_OreDictUnificator.setStackArray(true, aInputs);
+ GT_OreDictUnificator.setStackArray(true, aOutputs);
+
+ for (ItemStack tStack : aOutputs) GT_Utility.updateItemStack(tStack);
+
+ for (int i = 0; i < aChances.length; i++) if (aChances[i] <= 0) aChances[i] = 10000;
+ for (int i = 0; i < aFluidInputs.length; i++) aFluidInputs[i] = aFluidInputs[i].copy();
+ for (int i = 0; i < aFluidOutputs.length; i++) aFluidOutputs[i] = aFluidOutputs[i].copy();
+
+ if (aOptimize && aDuration >= 32) {
+ ArrayList<ItemStack> tList = new ArrayList<>();
+ tList.addAll(Arrays.asList(aInputs));
+ tList.addAll(Arrays.asList(aOutputs));
+ for (int i = 0; i < tList.size(); i++) if (tList.get(i) == null) tList.remove(i--);
+
+ for (byte i = (byte) Math.min(64, aDuration / 16); i > 1; i--) if (aDuration / i >= 16) {
+ boolean temp = true;
+ for (ItemStack stack : tList) if (stack.stackSize % i != 0) {
+ temp = false;
+ break;
+ }
+ if (temp) for (FluidStack aFluidInput : aFluidInputs) if (aFluidInput.amount % i != 0) {
+ temp = false;
+ break;
+ }
+ if (temp) for (FluidStack aFluidOutput : aFluidOutputs) if (aFluidOutput.amount % i != 0) {
+ temp = false;
+ break;
+ }
+ if (temp) {
+ for (ItemStack itemStack : tList) itemStack.stackSize /= i;
+ for (FluidStack aFluidInput : aFluidInputs) aFluidInput.amount /= i;
+ for (FluidStack aFluidOutput : aFluidOutputs) aFluidOutput.amount /= i;
+ aDuration /= i;
+ }
+ }
+ }
+
+ mInputs = aInputs;
+ mOutputs = aOutputs;
+ mSpecialItems = aSpecialItems;
+ mChances = aChances;
+ mFluidInputs = aFluidInputs;
+ mFluidOutputs = aFluidOutputs;
+ mDuration = aDuration;
+ mSpecialValue = aSpecialValue;
+ mEUt = aEUt;
+ metadataStorage = EmptyRecipeMetadataStorage.INSTANCE;
+ // checkCellBalance();
+ reloadOwner();
+ }
+
+ // aSpecialValue = EU per Liter! If there is no Liquid for this Object, then it gets multiplied with 1000!
+ public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, ItemStack aOutput2, ItemStack aOutput3, ItemStack aOutput4,
+ int aSpecialValue, int aType) {
+ this(
+ true,
+ new ItemStack[] { aInput1 },
+ new ItemStack[] { aOutput1, aOutput2, aOutput3, aOutput4 },
+ null,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ Math.max(1, aSpecialValue));
+
+ if (mInputs.length > 0 && aSpecialValue > 0) {
+ switch (aType) {
+ // Diesel Generator
+ case 0 -> {
+ RecipeMaps.dieselFuels.addRecipe(this);
+ RecipeMaps.largeBoilerFakeFuels.getBackend()
+ .addDieselRecipe(this);
+ }
+ // Gas Turbine
+ case 1 -> RecipeMaps.gasTurbineFuels.addRecipe(this);
+
+ // Thermal Generator
+ case 2 -> RecipeMaps.hotFuels.addRecipe(this);
+
+ // Plasma Generator
+ case 4 -> RecipeMaps.plasmaFuels.addRecipe(this);
+
+ // Magic Generator
+ case 5 -> RecipeMaps.magicFuels.addRecipe(this);
+
+ // Fluid Generator. Usually 3. Every wrong Type ends up in the Semifluid Generator
+ default -> {
+ RecipeMaps.denseLiquidFuels.addRecipe(this);
+ RecipeMaps.largeBoilerFakeFuels.getBackend()
+ .addDenseLiquidRecipe(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);
+ }
+
+ /**
+ * Re-unificates all the items present in recipes.
+ */
+ public static void reInit() {
+ GT_Log.out.println("GT_Mod: Re-Unificating Recipes.");
+ for (RecipeMap<?> map : RecipeMap.ALL_RECIPE_MAPS.values()) {
+ map.getBackend()
+ .reInit();
+ }
+ }
+
+ public ItemStack getRepresentativeInput(int aIndex) {
+ if (aIndex < 0 || aIndex >= mInputs.length) return null;
+ return GT_Utility.copyOrNull(mInputs[aIndex]);
+ }
+
+ public ItemStack getOutput(int aIndex) {
+ if (aIndex < 0 || aIndex >= mOutputs.length) return null;
+ return GT_Utility.copyOrNull(mOutputs[aIndex]);
+ }
+
+ /**
+ * Dictates the ItemStacks displayed in the output slots of any NEI page handled by the default GT NEI handler.
+ * Override to make shown items differ from a GT_Recipe's item output array
+ *
+ * @see gregtech.nei.GT_NEI_DefaultHandler
+ * @param i Slot index
+ * @return ItemStack to be displayed in the slot
+ */
+ public ItemStack getRepresentativeOutput(int i) {
+ return getOutput(i);
+ }
+
+ public int getOutputChance(int aIndex) {
+ if (mChances == null) return 10000;
+ if (aIndex < 0 || aIndex >= mChances.length) return 10000;
+ return mChances[aIndex];
+ }
+
+ public FluidStack getRepresentativeFluidInput(int aIndex) {
+ if (aIndex < 0 || aIndex >= mFluidInputs.length || mFluidInputs[aIndex] == null) return null;
+ return mFluidInputs[aIndex].copy();
+ }
+
+ public FluidStack getFluidOutput(int aIndex) {
+ if (aIndex < 0 || aIndex >= mFluidOutputs.length || mFluidOutputs[aIndex] == null) return null;
+ return mFluidOutputs[aIndex].copy();
+ }
+
+ public void checkCellBalance() {
+ if (!D2 || mInputs.length < 1) return;
+
+ int tInputAmount = GT_ModHandler.getCapsuleCellContainerCountMultipliedWithStackSize(mInputs);
+ int tOutputAmount = GT_ModHandler.getCapsuleCellContainerCountMultipliedWithStackSize(mOutputs);
+
+ if (tInputAmount < tOutputAmount) {
+ if (!Materials.Tin.contains(mInputs)) {
+ GT_Log.err.println("You get more Cells, than you put in? There must be something wrong.");
+ new Exception().printStackTrace(GT_Log.err);
+ }
+ } else if (tInputAmount > tOutputAmount) {
+ if (!Materials.Tin.contains(mOutputs)) {
+ GT_Log.err.println("You get less Cells, than you put in? GT Machines usually don't destroy Cells.");
+ new Exception().printStackTrace(GT_Log.err);
+ }
+ }
+ }
+
+ public GT_Recipe copy() {
+ return new GT_Recipe(this, false);
+ }
+
+ public GT_Recipe copyShallow() {
+ return new GT_Recipe(this, true);
+ }
+
+ public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, FluidStack[] aFluidInputs,
+ ItemStack... aInputs) {
+ return isRecipeInputEqual(aDecreaseStacksizeBySuccess, false, 1, aFluidInputs, aInputs);
+ }
+
+ // For non-multiplied recipe amount values
+ public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, boolean aDontCheckStackSizes,
+ FluidStack[] aFluidInputs, ItemStack... aInputs) {
+ return isRecipeInputEqual(aDecreaseStacksizeBySuccess, aDontCheckStackSizes, 1, aFluidInputs, aInputs);
+ }
+
+ /**
+ * Okay, did some code archeology to figure out what's going on here.
+ *
+ * <p>
+ * This variable was added in <a
+ * href=https://github.com/GTNewHorizons/GT5-Unofficial/commit/9959ab7443982a19ad329bca424ab515493432e9>this
+ * commit,</a> in order to fix the issues mentioned in <a
+ * href=https://github.com/GTNewHorizons/GT5-Unofficial/pull/183>the PR</a>.
+ *
+ * <p>
+ * It looks like it controls checking NBT. At this point, since we are still using universal fluid cells which store
+ * their fluids in NBT, it probably will not be safe to disable the NBT checks in the near future. Data sticks may
+ * be another case. Anyway, we probably can't get rid of this without some significant changes to clean up recipe
+ * inputs.
+ */
+ public static boolean GTppRecipeHelper;
+
+ /**
+ * WARNING: Do not call this method with both {@code aDecreaseStacksizeBySuccess} and {@code aDontCheckStackSizes}
+ * set to {@code true}! You'll get weird behavior.
+ */
+ public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, boolean aDontCheckStackSizes,
+ int amountMultiplier, FluidStack[] aFluidInputs, ItemStack... aInputs) {
+ double maxParallel = maxParallelCalculatedByInputs(amountMultiplier, aFluidInputs, aInputs);
+ if (aDontCheckStackSizes) {
+ return maxParallel > 0;
+ } else if (maxParallel >= amountMultiplier) {
+ if (aDecreaseStacksizeBySuccess) {
+ consumeInput(amountMultiplier, aFluidInputs, aInputs);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * WARNING: Ensure that item inputs and fluid inputs are enough to be consumed with
+ * {@link #maxParallelCalculatedByInputs} before calling this method!
+ */
+ public void consumeInput(int amountMultiplier, FluidStack[] aFluidInputs, ItemStack... aInputs) {
+ if (amountMultiplier <= 0) return;
+
+ long remainingCost;
+
+ if (aFluidInputs != null) {
+ for (FluidStack recipeFluidCost : mFluidInputs) {
+ if (recipeFluidCost != null) {
+ remainingCost = (long) recipeFluidCost.amount * amountMultiplier;
+
+ for (FluidStack providedFluid : aFluidInputs) {
+ if (providedFluid != null && providedFluid.isFluidEqual(recipeFluidCost)) {
+ if (providedFluid.amount >= remainingCost) {
+ providedFluid.amount -= remainingCost;
+ break;
+ } else {
+ remainingCost -= providedFluid.amount;
+ providedFluid.amount = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (aInputs != null) {
+ for (ItemStack recipeItemCost : mInputs) {
+ ItemStack unifiedItemCost = GT_OreDictUnificator.get_nocopy(true, recipeItemCost);
+ if (unifiedItemCost != null) {
+ remainingCost = (long) recipeItemCost.stackSize * amountMultiplier;
+
+ for (ItemStack providedItem : aInputs) {
+ if (isNBTSensitive && !GT_Utility.areStacksEqual(providedItem, unifiedItemCost, false)) {
+ continue;
+ } else if (!isNBTSensitive
+ && !GT_OreDictUnificator.isInputStackEqual(providedItem, unifiedItemCost)) {
+ continue;
+ }
+
+ if (GTppRecipeHelper) { // Please see JavaDoc on GTppRecipeHelper for why this is here.
+ if (GT_Utility.areStacksEqual(providedItem, Ic2Items.FluidCell.copy(), true)
+ || GT_Utility.areStacksEqual(providedItem, ItemList.Tool_DataStick.get(1L), true)
+ || GT_Utility.areStacksEqual(providedItem, ItemList.Tool_DataOrb.get(1L), true)) {
+ if (!GT_Utility.areStacksEqual(providedItem, recipeItemCost, false)) continue;
+ }
+ }
+
+ if (providedItem.stackSize >= remainingCost) {
+ providedItem.stackSize -= remainingCost;
+ break;
+ } else {
+ remainingCost -= providedItem.stackSize;
+ providedItem.stackSize = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the number of parallel recipes, or 0 if recipe is not satisfied at all. 0 < number < 1 means that inputs
+ * are found but not enough.
+ */
+ public double maxParallelCalculatedByInputs(int maxParallel, FluidStack[] aFluidInputs, ItemStack... aInputs) {
+ if (mInputs.length > 0 && aInputs == null) return 0;
+ if (mFluidInputs.length > 0 && aFluidInputs == null) return 0;
+
+ double currentParallel = maxParallel;
+
+ // We need to have any fluids inputs, otherwise the code below does nothing. The second check is always true
+ // because of early exit condition above.
+ if (mFluidInputs.length > 0 /* && aFluidInputs != null */) {
+ // Create map for fluid -> stored amount
+ Reference2LongMap<Fluid> fluidMap = new Reference2LongArrayMap<>(2);
+ Reference2LongMap<Fluid> fluidCost = new Reference2LongArrayMap<>(2);
+ for (FluidStack fluidStack : aFluidInputs) {
+ if (fluidStack == null) continue;
+ fluidMap.mergeLong(fluidStack.getFluid(), fluidStack.amount, Long::sum);
+ }
+ for (FluidStack fluidStack : mFluidInputs) {
+ if (fluidStack == null) continue;
+ fluidCost.mergeLong(fluidStack.getFluid(), fluidStack.amount, Long::sum);
+ }
+
+ // Check how many parallels can it perform for each fluid
+ for (Reference2LongMap.Entry<Fluid> costEntry : fluidCost.reference2LongEntrySet()) {
+ if (costEntry.getLongValue() > 0) {
+ currentParallel = Math.min(
+ currentParallel,
+ (double) fluidMap.getOrDefault(costEntry.getKey(), 0L) / costEntry.getLongValue());
+ }
+ if (currentParallel <= 0) {
+ return 0;
+ }
+ }
+ }
+
+ if (mInputs.length > 0) {
+ double remainingCost;
+ long providedAmount;
+ Object2LongMap<GT_Utility.ItemId> itemCostMap = new Object2LongArrayMap<>(2);
+
+ for (ItemStack itemStack : mInputs) {
+ if (itemStack == null) continue;
+ if (shouldCheckNBT(itemStack)) {
+ GT_Utility.ItemId itemId = GT_Utility.ItemId.createNoCopy(itemStack);
+ itemCostMap.mergeLong(itemId, itemStack.stackSize, Long::sum);
+ continue;
+ }
+ ItemStack unifiedItem = GT_OreDictUnificator.get_nocopy(true, itemStack);
+ if (unifiedItem != null) {
+ GT_Utility.ItemId unifiedId;
+ if (isNBTSensitive) unifiedId = GT_Utility.ItemId.createNoCopy(unifiedItem);
+ else unifiedId = GT_Utility.ItemId.createWithoutNBT(unifiedItem);
+ itemCostMap.mergeLong(unifiedId, itemStack.stackSize, Long::sum);
+ }
+ }
+
+ ItemStack unifiedItemCost;
+ nextRecipeItemCost: for (Map.Entry<GT_Utility.ItemId, Long> costEntry : itemCostMap.entrySet()) {
+ unifiedItemCost = costEntry.getKey()
+ .getItemStack();
+ if (unifiedItemCost != null) {
+ remainingCost = costEntry.getValue() * currentParallel;
+ providedAmount = 0;
+
+ for (ItemStack providedItem : aInputs) {
+ if (!areInputStackAndRecipeCostMatched(providedItem, unifiedItemCost)) continue;
+ // for non-consumed input
+ if (costEntry.getValue() == 0) continue nextRecipeItemCost;
+
+ providedAmount += providedItem.stackSize;
+
+ if (providedAmount >= remainingCost) continue nextRecipeItemCost;
+ }
+ if (providedAmount == 0) return 0;
+ currentParallel = Math.min(currentParallel, (double) providedAmount / costEntry.getValue());
+ }
+ }
+ }
+ return currentParallel;
+ }
+
+ private boolean areInputStackAndRecipeCostMatched(ItemStack providedItem, ItemStack unifiedItemCost) {
+ if (isNBTSensitive || shouldCheckNBT(providedItem)) {
+ return GT_Utility.areStacksEqual(providedItem, unifiedItemCost, false);
+ } else {
+ return GT_OreDictUnificator.isInputStackEqual(providedItem, unifiedItemCost);
+ }
+ }
+
+ /**
+ * Please see JavaDoc on {@link #GTppRecipeHelper} for why this is here.
+ */
+ private boolean shouldCheckNBT(ItemStack item) {
+ if (GTppRecipeHelper) {
+ return GT_Utility.areStacksEqual(item, Ic2Items.FluidCell.copy(), true)
+ || GT_Utility.areStacksEqual(item, ItemList.Tool_DataStick.get(1L), true)
+ || GT_Utility.areStacksEqual(item, ItemList.Tool_DataOrb.get(1L), true);
+ }
+ return false;
+ }
+
+ public boolean isRecipePossible(@Nullable ItemInventoryLogic itemInput, @Nullable FluidInventoryLogic fluidInput) {
+ return getAmountOfRecipesDone(itemInput, fluidInput, 1, true) > 0;
+ }
+
+ public long getAmountOfRecipesDone(@Nullable ItemInventoryLogic itemInput, @Nullable FluidInventoryLogic fluidInput,
+ long maxParallel, boolean simulate) {
+ if (itemInput == null) {
+ itemInput = new ItemInventoryLogic(0);
+ }
+
+ if (fluidInput == null) {
+ fluidInput = new FluidInventoryLogic(0, 0);
+ }
+
+ itemInput.startRecipeCheck();
+ Map<ItemHolder, Long> recipeItems = getItemInputsAsItemMap();
+ for (Entry<ItemHolder, Long> entry : recipeItems.entrySet()) {
+ maxParallel = Math
+ .min(maxParallel, itemInput.calculateAmountOfTimesItemCanBeTaken(entry.getKey(), entry.getValue()));
+ }
+
+ for (FluidStack fluid : mFluidInputs) {
+ if (fluid == null) continue;
+ maxParallel = Math
+ .min(maxParallel, fluidInput.calculateAmountOfTimesFluidCanBeTaken(fluid.getFluid(), fluid.amount));
+ }
+
+ if (simulate) {
+ itemInput.stopRecipeCheck();
+ return maxParallel;
+ }
+
+ for (Entry<ItemHolder, Long> entry : recipeItems.entrySet()) {
+ itemInput.subtractItemAmount(entry.getKey(), entry.getValue() * maxParallel, false);
+ }
+
+ for (FluidStack fluid : mFluidInputs) {
+ if (fluid == null) continue;
+ fluidInput.drain(fluid.getFluid(), fluid.amount * maxParallel, false);
+ }
+ itemInput.stopRecipeCheck();
+ return maxParallel;
+ }
+
+ private Map<ItemHolder, Long> getItemInputsAsItemMap() {
+ Map<ItemHolder, Long> items = new HashMap<>();
+ for (ItemStack item : mInputs) {
+ if (item == null) continue;
+ ItemHolder itemHolder = new ItemHolder(item);
+ items.put(itemHolder, items.getOrDefault(itemHolder, 0L) + item.stackSize);
+ }
+ return items;
+ }
+
+ @Override
+ public int compareTo(GT_Recipe recipe) {
+ // first lowest tier recipes
+ // then fastest
+ // then with lowest special value
+ // then dry recipes
+ // then with fewer inputs
+ if (this.mEUt != recipe.mEUt) {
+ return this.mEUt - recipe.mEUt;
+ } else if (this.mDuration != recipe.mDuration) {
+ return this.mDuration - recipe.mDuration;
+ } else if (this.mSpecialValue != recipe.mSpecialValue) {
+ return this.mSpecialValue - recipe.mSpecialValue;
+ } else if (this.mFluidInputs.length != recipe.mFluidInputs.length) {
+ return this.mFluidInputs.length - recipe.mFluidInputs.length;
+ } else if (this.mInputs.length != recipe.mInputs.length) {
+ return this.mInputs.length - recipe.mInputs.length;
+ }
+ return 0;
+ }
+
+ public String[] getNeiDesc() {
+ return neiDesc;
+ }
+
+ /**
+ * Sets description shown on NEI. <br>
+ * If you have a large number of recipes for the recipemap, this is not efficient memory wise, so use
+ * {@link gregtech.api.recipe.RecipeMapBuilder#neiSpecialInfoFormatter} instead.
+ */
+ public void setNeiDesc(String... neiDesc) {
+ this.neiDesc = neiDesc;
+ }
+
+ // region metadata
+
+ // Don't try implementing setMetadata, as metadataStorage can be EmptyRecipeMetadataStorage
+
+ /**
+ * Gets metadata associated with this recipe. Can return null. Use
+ * {@link #getMetadataOrDefault(RecipeMetadataKey, Object)}
+ * if you want to specify default value.
+ */
+ @Nullable
+ public <T> T getMetadata(RecipeMetadataKey<T> key) {
+ return key.cast(metadataStorage.getMetadata(key));
+ }
+
+ /**
+ * Gets metadata associated with this recipe with default value. Does not return null unless default value is null.
+ */
+ @Contract("_, !null -> !null")
+ @Nullable
+ public <T> T getMetadataOrDefault(RecipeMetadataKey<T> key, @Nullable T defaultValue) {
+ return key.cast(metadataStorage.getMetadataOrDefault(key, defaultValue));
+ }
+
+ @Nonnull
+ public IRecipeMetadataStorage getMetadataStorage() {
+ return metadataStorage;
+ }
+
+ // endregion
+
+ public RecipeCategory getRecipeCategory() {
+ return recipeCategory;
+ }
+
+ /**
+ * Exists only for recipe copying from external. For ordinal use case, use {@link GT_RecipeBuilder#recipeCategory}.
+ */
+ public void setRecipeCategory(RecipeCategory recipeCategory) {
+ this.recipeCategory = recipeCategory;
+ }
+
+ private static final List<String> excludedStacktraces = Arrays.asList(
+ "java.lang.Thread",
+ "gregtech.api.interfaces.IRecipeMap",
+ "gregtech.api.interfaces.IRecipeMap$1",
+ "gregtech.api.recipe.RecipeMap",
+ "gregtech.api.recipe.RecipeMapBackend",
+ "gregtech.api.recipe.RecipeMapBackendPropertiesBuilder",
+ "gregtech.api.util.GT_Recipe",
+ "gregtech.api.util.GT_RecipeBuilder",
+ "gregtech.api.util.GT_RecipeConstants",
+ "gregtech.api.util.GT_RecipeMapUtil",
+ "gregtech.common.GT_RecipeAdder");
+
+ public void reloadOwner() {
+ setOwner(
+ Loader.instance()
+ .activeModContainer());
+
+ if (GT_Mod.gregtechproxy.mNEIRecipeOwnerStackTrace) {
+ List<String> toAdd = new ArrayList<>();
+ for (StackTraceElement stackTrace : Thread.currentThread()
+ .getStackTrace()) {
+ if (excludedStacktraces.stream()
+ .noneMatch(
+ c -> stackTrace.getClassName()
+ .equals(c))) {
+ toAdd.add(formatStackTrace(stackTrace));
+ }
+ }
+ stackTraces.add(toAdd);
+ }
+ }
+
+ private static String formatStackTrace(StackTraceElement stackTraceElement) {
+ String raw = stackTraceElement.toString();
+ int startParen = raw.lastIndexOf('(');
+ int colon = raw.lastIndexOf(':');
+ if (colon == -1) {
+ // native or unknown source
+ return raw;
+ }
+ // strip class name and leave line number, as class name is already shown
+ return raw.substring(0, startParen + 1) + raw.substring(colon);
+ }
+
+ public void setOwner(ModContainer newOwner) {
+ ModContainer oldOwner = !owners.isEmpty() ? this.owners.get(owners.size() - 1) : null;
+ if (newOwner != null && newOwner != oldOwner) {
+ owners.add(newOwner);
+ }
+ }
+
+ /**
+ * Use in case {@link Loader#activeModContainer()} isn't helpful
+ */
+ public void setOwner(String modId) {
+ for (ModContainer mod : Loader.instance()
+ .getModList()) {
+ if (mod.getModId()
+ .equals(modId)) {
+ setOwner(mod);
+ return;
+ }
+ }
+ }
+
+ public GT_Recipe setInputs(ItemStack... aInputs) {
+ // TODO determine if we need this without trailing nulls call
+ this.mInputs = ArrayExt.withoutTrailingNulls(aInputs, ItemStack[]::new);
+ return this;
+ }
+
+ public GT_Recipe setOutputs(ItemStack... aOutputs) {
+ this.mOutputs = ArrayExt.withoutTrailingNulls(aOutputs, ItemStack[]::new);
+ return this;
+ }
+
+ public GT_Recipe setFluidInputs(FluidStack... aInputs) {
+ this.mFluidInputs = ArrayExt.withoutTrailingNulls(aInputs, FluidStack[]::new);
+ return this;
+ }
+
+ public GT_Recipe setFluidOutputs(FluidStack... aOutputs) {
+ this.mFluidOutputs = ArrayExt.withoutTrailingNulls(aOutputs, FluidStack[]::new);
+ return this;
+ }
+
+ public GT_Recipe setDuration(int aDuration) {
+ this.mDuration = aDuration;
+ return this;
+ }
+
+ public GT_Recipe setEUt(int aEUt) {
+ this.mEUt = aEUt;
+ return this;
+ }
+
+ public static class GT_Recipe_AssemblyLine {
+
+ public static final ArrayList<GT_Recipe_AssemblyLine> sAssemblylineRecipes = new ArrayList<>();
+
+ static {
+ if (!Boolean.getBoolean("com.gtnh.gt5u.ignore-invalid-assline-recipe"))
+ GregTech_API.sFirstWorldTick.add(GT_Recipe_AssemblyLine::checkInvalidRecipes);
+ else GT_Log.out.println("NOT CHECKING INVALID ASSLINE RECIPE.");
+ }
+
+ private static void checkInvalidRecipes() {
+ int invalidCount = 0;
+ GT_Log.out.println("Started assline validation");
+ for (GT_Recipe_AssemblyLine recipe : sAssemblylineRecipes) {
+ if (recipe.getPersistentHash() == 0) {
+ invalidCount++;
+ GT_Log.err.printf("Invalid recipe: %s%n", recipe);
+ }
+ }
+ if (invalidCount > 0) throw new RuntimeException(
+ "There are " + invalidCount + " invalid assembly line recipe(s)! Check GregTech.log for details!");
+ }
+
+ public ItemStack mResearchItem;
+ public int mResearchTime;
+ public ItemStack[] mInputs;
+ public FluidStack[] mFluidInputs;
+ public ItemStack mOutput;
+ public int mDuration;
+ public int mEUt;
+ public ItemStack[][] mOreDictAlt;
+ private int mPersistentHash;
+
+ /**
+ * THIS CONSTRUCTOR DOES SET THE PERSISTENT HASH.
+ * <p>
+ * if you set one yourself, it will give you one of the RunetimeExceptions!
+ */
+ public GT_Recipe_AssemblyLine(ItemStack aResearchItem, int aResearchTime, ItemStack[] aInputs,
+ FluidStack[] aFluidInputs, ItemStack aOutput, int aDuration, int aEUt) {
+ this(
+ aResearchItem,
+ aResearchTime,
+ aInputs,
+ aFluidInputs,
+ aOutput,
+ aDuration,
+ aEUt,
+ new ItemStack[aInputs.length][]);
+ int tPersistentHash = 1;
+ for (ItemStack tInput : aInputs)
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(tInput, true, false);
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aResearchItem, true, false);
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aOutput, true, false);
+ for (FluidStack tFluidInput : aFluidInputs)
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(tFluidInput, true, false);
+ tPersistentHash = tPersistentHash * 31 + aResearchTime;
+ tPersistentHash = tPersistentHash * 31 + aDuration;
+ tPersistentHash = tPersistentHash * 31 + aEUt;
+ setPersistentHash(tPersistentHash);
+ }
+
+ /**
+ * THIS CONSTRUCTOR DOES <b>NOT</b> SET THE PERSISTENT HASH.
+ * <p>
+ * if you don't set one yourself, it will break a lot of stuff!
+ */
+ public GT_Recipe_AssemblyLine(ItemStack aResearchItem, int aResearchTime, ItemStack[] aInputs,
+ FluidStack[] aFluidInputs, ItemStack aOutput, int aDuration, int aEUt, ItemStack[][] aAlt) {
+ mResearchItem = aResearchItem;
+ mResearchTime = aResearchTime;
+ mInputs = aInputs;
+ mFluidInputs = aFluidInputs;
+ mOutput = aOutput;
+ mDuration = aDuration;
+ 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 other)) {
+ return false;
+ }
+ 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;
+ }
+
+ public int getPersistentHash() {
+ if (mPersistentHash == 0)
+ GT_Log.err.println("Assline recipe persistent hash has not been set! Recipe: " + mOutput);
+ return mPersistentHash;
+ }
+
+ @Override
+ public String toString() {
+ return "GT_Recipe_AssemblyLine{" + "mResearchItem="
+ + mResearchItem
+ + ", mResearchTime="
+ + mResearchTime
+ + ", mInputs="
+ + Arrays.toString(mInputs)
+ + ", mFluidInputs="
+ + Arrays.toString(mFluidInputs)
+ + ", mOutput="
+ + mOutput
+ + ", mDuration="
+ + mDuration
+ + ", mEUt="
+ + mEUt
+ + ", mOreDictAlt="
+ + Arrays.toString(mOreDictAlt)
+ + '}';
+ }
+
+ /**
+ * @param aPersistentHash the persistent hash. it should reflect the exact input used to generate this recipe If
+ * 0 is passed in, the actual persistent hash will be automatically remapped to 1
+ * instead.
+ * @throws IllegalStateException if the persistent hash has been set already
+ */
+ public void setPersistentHash(int aPersistentHash) {
+ if (this.mPersistentHash != 0) throw new IllegalStateException("Cannot set persistent hash twice!");
+ if (aPersistentHash == 0) this.mPersistentHash = 1;
+ else this.mPersistentHash = aPersistentHash;
+ }
+
+ /**
+ * @param inputBusses List of input busses to check.
+ * @return An array containing the amount of item to consume from the first slot of every input bus.
+ * {@code null} if at least one item fails to match the recipe ingredient.
+ */
+ public static int[] getItemConsumptionAmountArray(ArrayList<GT_MetaTileEntity_Hatch_InputBus> inputBusses,
+ GT_Recipe_AssemblyLine recipe) {
+ int itemCount = recipe.mInputs.length;
+ if (itemCount == 0) return null;
+ int[] tStacks = new int[itemCount];
+ for (int i = 0; i < itemCount; i++) {
+ GT_MetaTileEntity_Hatch_InputBus inputBus = inputBusses.get(i);
+ if (!inputBus.isValid()) return null;
+ ItemStack slotStack;
+ if (inputBus instanceof GT_MetaTileEntity_Hatch_InputBus_ME meBus) {
+ slotStack = meBus.getShadowItemStack(0);
+ } else {
+ slotStack = inputBus.getStackInSlot(0);
+ }
+ if (slotStack == null) return null;
+
+ int amount = getMatchedIngredientAmount(slotStack, recipe.mInputs[i], recipe.mOreDictAlt[i]);
+ if (amount < 0) return null;
+
+ tStacks[i] = amount;
+ }
+ return tStacks;
+ }
+
+ public static int getMatchedIngredientAmount(ItemStack aSlotStack, ItemStack aIngredient, ItemStack[] alts) {
+ if (alts == null || alts.length == 0) {
+ if (GT_Utility.areStacksEqual(aSlotStack, aIngredient, true)) {
+ return aIngredient.stackSize;
+ }
+ return -1;
+ }
+ for (ItemStack tAltStack : alts) {
+ if (GT_Utility.areStacksEqual(aSlotStack, tAltStack, true)) {
+ return tAltStack.stackSize;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @param inputBusses Input bus list to check. Usually the input bus list of multi.
+ * @param itemConsumptions Should be generated by {@link GT_Recipe_AssemblyLine#getItemConsumptionAmountArray}.
+ * @Return The number of parallel recipes, or 0 if recipe is not satisfied at all. 0 < number < 1 means that
+ * inputs are found but not enough.
+ */
+ public static double maxParallelCalculatedByInputItems(ArrayList<GT_MetaTileEntity_Hatch_InputBus> inputBusses,
+ int maxParallel, int[] itemConsumptions, Map<GT_Utility.ItemId, ItemStack> inputsFromME) {
+ // Recipe item matching is done in the generation of itemConsumptions.
+
+ Map<GT_Utility.ItemId, Long> itemConsumptionsFromME = new Object2LongOpenHashMap<>();
+ double currentParallel = maxParallel;
+
+ // Calculate the amount of each item to consume from ME
+ for (int i = 0; i < itemConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_InputBus inputBus = inputBusses.get(i);
+ if (!inputBus.isValid()) return 0;
+ if (inputBus instanceof GT_MetaTileEntity_Hatch_InputBus_ME meBus) {
+ ItemStack item = meBus.getShadowItemStack(0);
+ if (item == null) return 0;
+ GT_Utility.ItemId id = GT_Utility.ItemId.createNoCopy(item);
+ itemConsumptionsFromME.merge(id, (long) itemConsumptions[i], Long::sum);
+ }
+ }
+ // Calculate parallel from ME input busses
+ for (Entry<GT_Utility.ItemId, Long> entry : itemConsumptionsFromME.entrySet()) {
+ if (!inputsFromME.containsKey(entry.getKey())) return 0;
+ long consume = entry.getValue();
+ // For non-consumed inputs
+ if (consume == 0) continue;
+ currentParallel = Math
+ .min(currentParallel, (double) inputsFromME.get(entry.getKey()).stackSize / consume);
+ if (currentParallel <= 0) return 0;
+ }
+
+ // Calculate parallel from regular input busses
+ for (int i = 0; i < itemConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_InputBus inputBus = inputBusses.get(i);
+ if (!inputBus.isValid()) return 0;
+ if (inputBus instanceof GT_MetaTileEntity_Hatch_InputBus_ME) continue;
+
+ ItemStack item = inputBus.getStackInSlot(0);
+ if (item == null) return 0;
+ // For non-consumed inputs
+ if (itemConsumptions[i] == 0) continue;
+ currentParallel = Math.min(currentParallel, (double) item.stackSize / itemConsumptions[i]);
+ if (currentParallel <= 0) return 0;
+ }
+ return currentParallel;
+ }
+
+ /**
+ * @param inputHatches Input hatch list to check. Usually the input hatch list of multi.
+ * @param fluidConsumptions Fluid inputs of the recipe.
+ * @return The number of parallel recipes, or 0 if recipe is not satisfied at all. 0 < number < 1 means that
+ * fluids are found but not enough.
+ */
+ public static double maxParallelCalculatedByInputFluids(ArrayList<GT_MetaTileEntity_Hatch_Input> inputHatches,
+ int maxParallel, FluidStack[] fluidConsumptions, Map<Fluid, FluidStack> fluidsFromME) {
+ Map<Fluid, Long> fluidConsumptionsFromME = new Reference2LongOpenHashMap<>();
+ double currentParallel = maxParallel;
+
+ // Calculate the amount of each fluid to consume from ME
+ for (int i = 0; i < fluidConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_Input inputHatch = inputHatches.get(i);
+ if (!inputHatch.isValid()) return 0;
+ if (inputHatch instanceof GT_MetaTileEntity_Hatch_Input_ME meHatch) {
+ FluidStack fluid = meHatch.getShadowFluidStack(0);
+ if (fluid == null) return 0;
+ if (!GT_Utility.areFluidsEqual(fluid, fluidConsumptions[i])) return 0;
+ fluidConsumptionsFromME.merge(fluid.getFluid(), (long) fluidConsumptions[i].amount, Long::sum);
+ }
+ }
+ // Calculate parallel from ME input hatches
+ for (Entry<Fluid, Long> entry : fluidConsumptionsFromME.entrySet()) {
+ Fluid fluid = entry.getKey();
+ if (!fluidsFromME.containsKey(fluid)) return 0;
+ long consume = entry.getValue();
+ currentParallel = Math.min(currentParallel, (double) fluidsFromME.get(fluid).amount / consume);
+ if (currentParallel <= 0) return 0;
+ }
+
+ // Calculate parallel from regular input hatches
+ for (int i = 0; i < fluidConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_Input inputHatch = inputHatches.get(i);
+ if (!inputHatch.isValid()) return 0;
+ if (inputHatch instanceof GT_MetaTileEntity_Hatch_Input_ME) continue;
+
+ FluidStack fluid;
+ if (inputHatch instanceof GT_MetaTileEntity_Hatch_MultiInput multiInput) {
+ fluid = multiInput.getFluid(0);
+ } else {
+ fluid = inputHatch.getFillableStack();
+ }
+ if (fluid == null) return 0;
+ if (!GT_Utility.areFluidsEqual(fluid, fluidConsumptions[i])) return 0;
+ currentParallel = Math.min(currentParallel, (double) fluid.amount / fluidConsumptions[i].amount);
+ if (currentParallel <= 0) return 0;
+ }
+ return currentParallel;
+ }
+
+ /**
+ * WARNING: Ensure that item inputs are enough to be consumed with
+ * {@link GT_Recipe_AssemblyLine#maxParallelCalculatedByInputItems} before calling this method!
+ *
+ * @param inputBusses Input bus list to check. Usually the input bus list of multi.
+ * @param itemConsumptions Should be generated by {@link GT_Recipe_AssemblyLine#getItemConsumptionAmountArray}.
+ */
+ public static void consumeInputItems(ArrayList<GT_MetaTileEntity_Hatch_InputBus> inputBusses,
+ int amountMultiplier, int[] itemConsumptions, Map<GT_Utility.ItemId, ItemStack> inputsFromME) {
+ for (int i = 0; i < itemConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_InputBus inputBus = inputBusses.get(i);
+ if (!inputBus.isValid()) continue;
+ ItemStack item;
+ if (inputBus instanceof GT_MetaTileEntity_Hatch_InputBus_ME meBus) {
+ item = inputsFromME.get(GT_Utility.ItemId.createNoCopy(meBus.getShadowItemStack(0)));
+ } else {
+ item = inputBus.getStackInSlot(0);
+ }
+ item.stackSize -= itemConsumptions[i] * amountMultiplier;
+ }
+ }
+
+ /**
+ * WARNING: Ensure that fluid inputs are enough to be consumed with
+ * {@link GT_Recipe_AssemblyLine#maxParallelCalculatedByInputFluids} before calling this method!
+ *
+ * @param inputHatches Input hatch list to check. Usually the input hatch list of multi.
+ * @param fluidConsumptions Fluid inputs of the recipe.
+ */
+ public static void consumeInputFluids(ArrayList<GT_MetaTileEntity_Hatch_Input> inputHatches,
+ int amountMultiplier, FluidStack[] fluidConsumptions, Map<Fluid, FluidStack> fluidsFromME) {
+ for (int i = 0; i < fluidConsumptions.length; i++) {
+ GT_MetaTileEntity_Hatch_Input inputHatch = inputHatches.get(i);
+ if (!inputHatch.isValid()) continue;
+ FluidStack fluid;
+ if (inputHatch instanceof GT_MetaTileEntity_Hatch_Input_ME meHatch) {
+ fluid = fluidsFromME.get(
+ meHatch.getShadowFluidStack(0)
+ .getFluid());
+ } else if (inputHatch instanceof GT_MetaTileEntity_Hatch_MultiInput multiInput) {
+ fluid = multiInput.getFluid(0);
+ } else {
+ fluid = inputHatch.getFillableStack();
+ }
+ fluid.amount -= fluidConsumptions[i].amount * amountMultiplier;
+ }
+ }
+ }
+
+ public static class GT_Recipe_WithAlt extends GT_Recipe {
+
+ public ItemStack[][] mOreDictAlt;
+
+ /**
+ * Only for {@link GT_RecipeBuilder}.
+ */
+ GT_Recipe_WithAlt(ItemStack[] mInputs, ItemStack[] mOutputs, FluidStack[] mFluidInputs,
+ FluidStack[] mFluidOutputs, int[] mChances, Object mSpecialItems, int mDuration, int mEUt,
+ int mSpecialValue, boolean mEnabled, boolean mHidden, boolean mFakeRecipe, boolean mCanBeBuffered,
+ boolean mNeedsEmptyOutput, boolean nbtSensitive, String[] neiDesc,
+ @Nullable IRecipeMetadataStorage metadataStorage, RecipeCategory recipeCategory,
+ ItemStack[][] mOreDictAlt) {
+ super(
+ mInputs,
+ mOutputs,
+ mFluidInputs,
+ mFluidOutputs,
+ mChances,
+ mSpecialItems,
+ mDuration,
+ mEUt,
+ mSpecialValue,
+ mEnabled,
+ mHidden,
+ mFakeRecipe,
+ mCanBeBuffered,
+ mNeedsEmptyOutput,
+ nbtSensitive,
+ neiDesc,
+ metadataStorage,
+ recipeCategory);
+ this.mOreDictAlt = mOreDictAlt;
+ }
+
+ public GT_Recipe_WithAlt(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems,
+ int[] aChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
+ int aSpecialValue, ItemStack[][] aAlt) {
+ super(
+ aOptimize,
+ aInputs,
+ aOutputs,
+ aSpecialItems,
+ aChances,
+ aFluidInputs,
+ aFluidOutputs,
+ aDuration,
+ aEUt,
+ aSpecialValue);
+ mOreDictAlt = aAlt;
+ }
+
+ public Object getAltRepresentativeInput(int aIndex) {
+ if (aIndex < 0) return null;
+ if (aIndex < mOreDictAlt.length) {
+ if (mOreDictAlt[aIndex] != null && mOreDictAlt[aIndex].length > 0) {
+ ItemStack[] rStacks = new ItemStack[mOreDictAlt[aIndex].length];
+ for (int i = 0; i < mOreDictAlt[aIndex].length; i++) {
+ rStacks[i] = GT_Utility.copyOrNull(mOreDictAlt[aIndex][i]);
+ }
+ return rStacks;
+ }
+ }
+ if (aIndex >= mInputs.length) return null;
+ return GT_Utility.copyOrNull(mInputs[aIndex]);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_RecipeBuilder.java b/src/main/java/gregtech/api/util/GT_RecipeBuilder.java
new file mode 100644
index 0000000000..6f7c9a81bb
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_RecipeBuilder.java
@@ -0,0 +1,933 @@
+package gregtech.api.util;
+
+import static gregtech.api.util.GT_RecipeMapUtil.SPECIAL_VALUE_ALIASES;
+import static gregtech.api.util.GT_Utility.copyFluidArray;
+import static gregtech.api.util.GT_Utility.copyItemArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.launchwrapper.Launch;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.Contract;
+
+import gregtech.GT_Mod;
+import gregtech.api.enums.Mods;
+import gregtech.api.interfaces.IRecipeMap;
+import gregtech.api.recipe.RecipeCategory;
+import gregtech.api.recipe.RecipeMetadataKey;
+import gregtech.api.recipe.metadata.IRecipeMetadataStorage;
+import gregtech.api.recipe.metadata.RecipeMetadataStorage;
+import gregtech.api.util.extensions.ArrayExt;
+
+@SuppressWarnings({ "unused", "UnusedReturnValue" })
+public class GT_RecipeBuilder {
+
+ // debug mode expose problems. panic mode help you check nothing is wrong-ish without you actively monitoring
+ private static final boolean DEBUG_MODE_NULL;
+ private static boolean PANIC_MODE_NULL;
+ private static final boolean DEBUG_MODE_INVALID;
+ private static final boolean PANIC_MODE_INVALID;
+ private static final boolean DEBUG_MODE_COLLISION;
+ private static final boolean PANIC_MODE_COLLISION;
+
+ public static final int WILDCARD = 32767;
+
+ // time units
+ public static final int HOURS = 20 * 60 * 60;
+ public static final int MINUTES = 20 * 60;
+ public static final int SECONDS = 20;
+ public static final int TICKS = 1;
+
+ // fluid units
+ public static final int INGOTS = 144;
+ public static final int HALF_INGOT = 72;
+ public static final int QUARTER_INGOT = 36;
+ public static final int EIGHTH_INGOT = 18;
+ public static final int NUGGETS = 16;
+ public static final int BUCKETS = 1000;
+
+ static {
+ final boolean debugAll;
+ if (System.getProperties()
+ .containsKey("gt.recipebuilder.debug")) {
+ debugAll = Boolean.getBoolean("gt.recipebuilder.debug");
+ } else {
+ // turn on debug by default in dev mode
+ debugAll = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
+ }
+ DEBUG_MODE_NULL = debugAll || Boolean.getBoolean("gt.recipebuilder.debug.null");
+ DEBUG_MODE_INVALID = debugAll || Boolean.getBoolean("gt.recipebuilder.debug.invalid");
+ DEBUG_MODE_COLLISION = debugAll || Boolean.getBoolean("gt.recipebuilder.debug.collision");
+
+ final boolean panicAll = Boolean.getBoolean("gt.recipebuilder.panic");
+ PANIC_MODE_NULL = panicAll || Boolean.getBoolean("gt.recipebuilder.panic.null");
+ PANIC_MODE_INVALID = panicAll || Boolean.getBoolean("gt.recipebuilder.panic.invalid");
+ PANIC_MODE_COLLISION = panicAll || Boolean.getBoolean("gt.recipebuilder.panic.collision");
+ }
+
+ protected ItemStack[] inputsBasic = new ItemStack[0];
+ protected Object[] inputsOreDict;
+ protected ItemStack[] outputs = new ItemStack[0];
+ protected ItemStack[][] alts;
+ protected FluidStack[] fluidInputs = new FluidStack[0];
+ protected FluidStack[] fluidOutputs = new FluidStack[0];
+ protected int[] chances;
+ protected Object special;
+ protected int duration = -1;
+ protected int eut = -1;
+ protected int specialValue;
+ protected boolean enabled = true;
+ protected boolean hidden = false;
+ protected boolean fakeRecipe = false;
+ protected boolean mCanBeBuffered = true;
+ protected boolean mNeedsEmptyOutput = false;
+ protected boolean nbtSensitive = false;
+ protected String[] neiDesc;
+ protected RecipeCategory recipeCategory;
+ protected boolean optimize = true;
+ @Nullable
+ protected IRecipeMetadataStorage metadataStorage;
+ protected boolean checkForCollision = true;
+ /**
+ * If recipe addition should be skipped.
+ */
+ protected boolean skip = false;
+ protected boolean valid = true;
+
+ GT_RecipeBuilder() {}
+
+ private GT_RecipeBuilder(ItemStack[] inputsBasic, Object[] inputsOreDict, ItemStack[] outputs, ItemStack[][] alts,
+ FluidStack[] fluidInputs, FluidStack[] fluidOutputs, int[] chances, Object special, int duration, int eut,
+ int specialValue, boolean enabled, boolean hidden, boolean fakeRecipe, boolean mCanBeBuffered,
+ boolean mNeedsEmptyOutput, boolean nbtSensitive, String[] neiDesc, RecipeCategory recipeCategory,
+ boolean optimize, @Nullable IRecipeMetadataStorage metadataStorage, boolean checkForCollision, boolean skip,
+ boolean valid) {
+ this.inputsBasic = inputsBasic;
+ this.inputsOreDict = inputsOreDict;
+ this.outputs = outputs;
+ this.alts = alts;
+ this.fluidInputs = fluidInputs;
+ this.fluidOutputs = fluidOutputs;
+ this.chances = chances;
+ this.special = special;
+ this.duration = duration;
+ this.eut = eut;
+ this.specialValue = specialValue;
+ this.enabled = enabled;
+ this.hidden = hidden;
+ this.fakeRecipe = fakeRecipe;
+ this.mCanBeBuffered = mCanBeBuffered;
+ this.mNeedsEmptyOutput = mNeedsEmptyOutput;
+ this.nbtSensitive = nbtSensitive;
+ this.neiDesc = neiDesc;
+ this.recipeCategory = recipeCategory;
+ this.optimize = optimize;
+ this.metadataStorage = metadataStorage;
+ if (this.metadataStorage != null) {
+ this.metadataStorage = this.metadataStorage.copy();
+ }
+ this.checkForCollision = checkForCollision;
+ this.skip = skip;
+ this.valid = valid;
+ }
+
+ // region helper methods
+
+ private static FluidStack[] fix(FluidStack[] fluidInputs) {
+ return Arrays.stream(fluidInputs)
+ .filter(Objects::nonNull)
+ .map(FluidStack::copy)
+ .toArray(FluidStack[]::new);
+ }
+
+ private static ItemStack[] fix(ItemStack[] inputs) {
+ return GT_OreDictUnificator.setStackArray(true, ArrayExt.withoutTrailingNulls(inputs, ItemStack[]::new));
+ }
+
+ public static GT_RecipeBuilder builder() {
+ return new GT_RecipeBuilder();
+ }
+
+ /**
+ * Creates empty builder where only duration and EU/t are set to 0.
+ */
+ public static GT_RecipeBuilder empty() {
+ return new GT_RecipeBuilder().duration(0)
+ .eut(0);
+ }
+
+ private static boolean containsNull(Object[] arr) {
+ return arr == null || Arrays.stream(arr)
+ .anyMatch(Objects::isNull);
+ }
+
+ private static void handleNullRecipeComponents(String componentType) {
+ // place a breakpoint here to catch all these issues
+ GT_Log.err.print("null detected in ");
+ GT_Log.err.println(componentType);
+ new NullPointerException().printStackTrace(GT_Log.err);
+ if (PANIC_MODE_NULL) {
+ throw new IllegalArgumentException("null in argument");
+ }
+ }
+
+ private static boolean debugNull() {
+ return DEBUG_MODE_NULL || PANIC_MODE_NULL;
+ }
+
+ public static void handleInvalidRecipe() {
+ if (!DEBUG_MODE_INVALID && !PANIC_MODE_INVALID) {
+ return;
+ }
+ // place a breakpoint here to catch all these issues
+ GT_Log.err.print("invalid recipe");
+ new IllegalArgumentException().printStackTrace(GT_Log.err);
+ if (PANIC_MODE_INVALID) {
+ throw new IllegalArgumentException("invalid recipe");
+ }
+ }
+
+ public static void handleRecipeCollision(String details) {
+ if (!DEBUG_MODE_COLLISION && !PANIC_MODE_COLLISION) {
+ return;
+ }
+ GT_Log.err.print("Recipe collision resulting in recipe loss detected with ");
+ GT_Log.err.println(details);
+ if (PANIC_MODE_COLLISION) {
+ throw new IllegalArgumentException("Recipe Collision");
+ } else {
+ // place a breakpoint here to catch all these issues
+ new IllegalArgumentException().printStackTrace(GT_Log.err);
+ }
+ }
+
+ public static void onConfigLoad() {
+ PANIC_MODE_NULL |= GT_Mod.gregtechproxy.crashOnNullRecipeInput;
+ }
+
+ // endregion
+
+ // region setter
+
+ /**
+ * Non-OreDicted item inputs. Assumes input is unified.
+ */
+ public GT_RecipeBuilder itemInputsUnified(ItemStack... inputs) {
+ if (skip) return this;
+ if (debugNull() && containsNull(inputs)) handleNullRecipeComponents("itemInputUnified");
+ inputsBasic = ArrayExt.withoutTrailingNulls(inputs, ItemStack[]::new);
+ inputsOreDict = null;
+ alts = null;
+ return this;
+ }
+
+ /**
+ * Non-OreDicted item inputs. Assumes input is not unified.
+ */
+ public GT_RecipeBuilder itemInputs(ItemStack... inputs) {
+ if (skip) return this;
+ if (debugNull() && containsNull(inputs)) handleNullRecipeComponents("itemInputs");
+ inputsBasic = fix(inputs);
+ inputsOreDict = null;
+ alts = null;
+ return this;
+ }
+
+ /**
+ * OreDicted item inputs. Currently only used for assline recipes adder.
+ */
+ public GT_RecipeBuilder itemInputs(Object... inputs) {
+ if (skip) return this;
+ inputsOreDict = inputs;
+ alts = new ItemStack[inputs.length][];
+ for (int i = 0, inputsLength = inputs.length; i < inputsLength; i++) {
+ Object input = inputs[i];
+ if (input instanceof ItemStack) {
+ alts[i] = new ItemStack[] { (ItemStack) input };
+ } else if (input instanceof ItemStack[]) {
+ alts[i] = ((ItemStack[]) input).clone();
+ } else if (input instanceof Object[]arr) {
+ if (arr.length != 2) continue;
+ List<ItemStack> ores = GT_OreDictUnificator.getOres(arr[0]);
+ if (ores.isEmpty()) continue;
+ int size = ((Number) arr[1]).intValue();
+ alts[i] = ores.stream()
+ .map(s -> GT_Utility.copyAmount(size, s))
+ .filter(GT_Utility::isStackValid)
+ .toArray(ItemStack[]::new);
+ } else if (input == null) {
+ handleNullRecipeComponents("recipe oredict input");
+ alts[i] = new ItemStack[0];
+ } else {
+ throw new IllegalArgumentException("index " + i + ", unexpected type: " + input.getClass());
+ }
+ }
+ inputsBasic = Arrays.stream(alts)
+ .map(ss -> ss.length > 0 ? ss[0] : null)
+ .toArray(ItemStack[]::new);
+ // optimize cannot handle recipes with alts
+ return noOptimize();
+ }
+
+ public GT_RecipeBuilder itemOutputs(ItemStack... outputs) {
+ if (skip) return this;
+ if (debugNull() && containsNull(outputs)) handleNullRecipeComponents("itemOutputs");
+ this.outputs = outputs;
+ if (chances != null && chances.length != outputs.length) {
+ throw new IllegalArgumentException("Output chances array and items array length differs");
+ }
+ return this;
+ }
+
+ /**
+ * Not intended to be used by recipe authors.
+ * Intended for recipe rewrite middlewares.
+ */
+ public GT_RecipeBuilder itemOutputs(ItemStack[] outputs, int[] chances) {
+ if (skip) return this;
+ if (debugNull() && containsNull(outputs)) handleNullRecipeComponents("itemOutputs");
+ this.outputs = outputs;
+ this.chances = chances;
+ if (chances != null && chances.length != outputs.length) {
+ throw new IllegalArgumentException("Output chances array and items array length differs");
+ }
+ return this;
+ }
+
+ public GT_RecipeBuilder fluidInputs(FluidStack... fluidInputs) {
+ if (skip) return this;
+ if (debugNull() && containsNull(fluidInputs)) handleNullRecipeComponents("fluidInputs");
+ this.fluidInputs = fix(fluidInputs);
+ return this;
+ }
+
+ public GT_RecipeBuilder fluidOutputs(FluidStack... fluidOutputs) {
+ if (skip) return this;
+ if (debugNull() && containsNull(fluidOutputs)) handleNullRecipeComponents("fluidOutputs");
+ this.fluidOutputs = fix(fluidOutputs);
+ return this;
+ }
+
+ public GT_RecipeBuilder outputChances(int... chances) {
+ if (skip) return this;
+ if (outputs != null && chances.length != outputs.length) {
+ throw new IllegalArgumentException("Output chances array and items array length differs");
+ }
+ this.chances = chances;
+ return this;
+ }
+
+ public GT_RecipeBuilder special(Object special) {
+ this.special = special;
+ return this;
+ }
+
+ /**
+ * Really just {@link #special(Object)}, but with a different signature to make it less confusing. WARNING: only for
+ * legacy recipe map. do not abuse.
+ */
+ public GT_RecipeBuilder specialItem(ItemStack specialItem) {
+ return special(specialItem);
+ }
+
+ public GT_RecipeBuilder duration(int duration) {
+ this.duration = duration;
+ return this;
+ }
+
+ public GT_RecipeBuilder duration(long duration) {
+ this.duration = (int) duration;
+ return this;
+ }
+
+ public GT_RecipeBuilder eut(int eut) {
+ this.eut = eut;
+ return this;
+ }
+
+ public GT_RecipeBuilder eut(long eut) {
+ this.eut = (int) eut;
+ return this;
+ }
+
+ /**
+ * prefer to use metadata over this. should only use when the target recipe map does not yet support metadata
+ * system, or it's to bridge legacy code and modern code.
+ */
+ public GT_RecipeBuilder specialValue(int specialValue) {
+ this.specialValue = specialValue;
+ return this;
+ }
+
+ // I don't expect anyone to actually call this...
+ public GT_RecipeBuilder disabled() {
+ this.enabled = false;
+ return this;
+ }
+
+ public GT_RecipeBuilder hidden() {
+ this.hidden = true;
+ return this;
+ }
+
+ public GT_RecipeBuilder fake() {
+ this.fakeRecipe = true;
+ return this;
+ }
+
+ public GT_RecipeBuilder noBuffer() {
+ this.mCanBeBuffered = false;
+ return this;
+ }
+
+ public GT_RecipeBuilder needsEmptyOutput() {
+ this.mNeedsEmptyOutput = true;
+ return this;
+ }
+
+ public GT_RecipeBuilder nbtSensitive() {
+ this.nbtSensitive = true;
+ return this;
+ }
+
+ public GT_RecipeBuilder setNEIDesc(String... neiDesc) {
+ this.neiDesc = neiDesc;
+ return this;
+ }
+
+ public GT_RecipeBuilder recipeCategory(RecipeCategory recipeCategory) {
+ this.recipeCategory = recipeCategory;
+ return this;
+ }
+
+ /**
+ * Prevent the resulting recipe from optimizing recipe, which is a process that reduce recipe batch size.
+ */
+ public GT_RecipeBuilder noOptimize() {
+ this.optimize = false;
+ return this;
+ }
+
+ /**
+ * Prevents checking collision with existing recipes when adding the built recipe.
+ */
+ public GT_RecipeBuilder ignoreCollision() {
+ this.checkForCollision = false;
+ return this;
+ }
+
+ /**
+ * Sets metadata of the recipe. It can be used for recipe emitter to do special things, or for being stored in the
+ * built recipe and used for actual recipe processing.
+ * <p>
+ * {@link GT_RecipeConstants} has a series of metadata keys. Or you can create one by yourself.
+ */
+ public <T> GT_RecipeBuilder metadata(RecipeMetadataKey<T> key, T value) {
+ if (skip) return this;
+ if (metadataStorage == null) {
+ metadataStorage = new RecipeMetadataStorage();
+ }
+ metadataStorage.store(key, value);
+ return this;
+ }
+
+ /**
+ * Gets metadata already set for this builder. Can return null. Use
+ * {@link #getMetadataOrDefault(RecipeMetadataKey, Object)}
+ * if you want to specify default value.
+ */
+ @Nullable
+ public <T> T getMetadata(RecipeMetadataKey<T> key) {
+ if (metadataStorage == null) {
+ return null;
+ }
+ return key.cast(metadataStorage.getMetadata(key));
+ }
+
+ /**
+ * Gets metadata already set for this builder with default value. Does not return null unless default value is null.
+ */
+ @Contract("_, !null -> !null")
+ @Nullable
+ public <T> T getMetadataOrDefault(RecipeMetadataKey<T> key, T defaultValue) {
+ if (metadataStorage == null) {
+ return defaultValue;
+ }
+ return key.cast(metadataStorage.getMetadataOrDefault(key, defaultValue));
+ }
+
+ /**
+ * Specifies mods required to add the recipe. If any of the mods is not loaded, all the operations for this builder
+ * will be ignored.
+ *
+ * @param mods Mod(s) required for the recipe.
+ */
+ public GT_RecipeBuilder requireMods(Mods... mods) {
+ skip = Stream.of(mods)
+ .anyMatch(mod -> !mod.isModLoaded());
+ return this;
+ }
+
+ public GT_RecipeBuilder requiresCleanRoom() {
+ return metadata(GT_RecipeConstants.CLEANROOM, true);
+ }
+
+ public GT_RecipeBuilder requiresLowGravity() {
+ return metadata(GT_RecipeConstants.LOW_GRAVITY, true);
+ }
+
+ // endregion
+
+ private static <T> T[] copy(T[] arr) {
+ return arr == null ? null : arr.clone();
+ }
+
+ private static int[] copy(int[] arr) {
+ return arr == null ? null : arr.clone();
+ }
+
+ /**
+ * produce a deep copy of current values. anything unset will remain unset. IMPORTANT: If metadata contains mutable
+ * value, they will not be cloned!
+ * <p>
+ * checkout docs/RecipeBuilder.md for more info on whether to copy or not.
+ */
+ public GT_RecipeBuilder copy() {
+ return new GT_RecipeBuilder(
+ copyItemArray(inputsBasic),
+ copy(inputsOreDict),
+ copyItemArray(outputs),
+ copy(alts),
+ copyFluidArray(fluidInputs),
+ copyFluidArray(fluidOutputs),
+ copy(chances),
+ special,
+ duration,
+ eut,
+ specialValue,
+ enabled,
+ hidden,
+ fakeRecipe,
+ mCanBeBuffered,
+ mNeedsEmptyOutput,
+ nbtSensitive,
+ copy(neiDesc),
+ recipeCategory,
+ optimize,
+ metadataStorage,
+ checkForCollision,
+ skip,
+ valid);
+ }
+
+ /**
+ * produce a deep copy of current values. anything unset will remain unset. discard all existing metadata
+ */
+ public GT_RecipeBuilder copyNoMetadata() {
+ return new GT_RecipeBuilder(
+ copyItemArray(inputsBasic),
+ copy(inputsOreDict),
+ copyItemArray(outputs),
+ copy(alts),
+ copyFluidArray(fluidInputs),
+ copyFluidArray(fluidOutputs),
+ copy(chances),
+ special,
+ duration,
+ eut,
+ specialValue,
+ enabled,
+ hidden,
+ fakeRecipe,
+ mCanBeBuffered,
+ mNeedsEmptyOutput,
+ nbtSensitive,
+ copy(neiDesc),
+ recipeCategory,
+ optimize,
+ null,
+ checkForCollision,
+ skip,
+ valid);
+ }
+
+ // region getter
+
+ public ItemStack getItemInputBasic(int index) {
+ return index < inputsBasic.length ? inputsBasic[index] : null;
+ }
+
+ public Object getItemInputOreDict(int index) {
+ return index < inputsOreDict.length ? inputsOreDict[index] : null;
+ }
+
+ public ItemStack getItemOutput(int index) {
+ return index < outputs.length ? outputs[index] : null;
+ }
+
+ public FluidStack getFluidInput(int index) {
+ return index < fluidInputs.length ? fluidInputs[index] : null;
+ }
+
+ public FluidStack getFluidOutput(int index) {
+ return index < fluidOutputs.length ? fluidOutputs[index] : null;
+ }
+
+ public ItemStack[] getItemInputsBasic() {
+ return inputsBasic;
+ }
+
+ public Object[] getItemInputsOreDict() {
+ return inputsOreDict;
+ }
+
+ public ItemStack[] getItemOutputs() {
+ return outputs;
+ }
+
+ public FluidStack[] getFluidInputs() {
+ return fluidInputs;
+ }
+
+ public FluidStack[] getFluidOutputs() {
+ return fluidOutputs;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public int[] getChances() {
+ return chances;
+ }
+
+ public int getEUt() {
+ return eut;
+ }
+
+ public RecipeCategory getRecipeCategory() {
+ return recipeCategory;
+ }
+
+ public boolean isOptimize() {
+ return optimize;
+ }
+
+ public boolean isCheckForCollision() {
+ return checkForCollision;
+ }
+
+ // endregion
+
+ // region validator
+
+ public GT_RecipeBuilder clearInvalid() {
+ valid = true;
+ return this;
+ }
+
+ public GT_RecipeBuilder invalidate() {
+ valid = false;
+ return this;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+
+ private static boolean isArrayValid(@Nonnull Object[] arr, int min, int max) {
+ int count = 0;
+ for (Object o : arr) {
+ if (o != null) count += 1;
+ }
+ return min <= count && max >= count;
+ }
+
+ /**
+ * Validate if input item match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateNoInput() {
+ if (skip) return this;
+ return GT_Utility.isArrayEmptyOrNull(inputsBasic) ? this : invalidate();
+ }
+
+ /**
+ * Validate if input fluid match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateNoInputFluid() {
+ if (skip) return this;
+ return GT_Utility.isArrayEmptyOrNull(fluidInputs) ? this : invalidate();
+ }
+
+ /**
+ * Validate if output item match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateNoOutput() {
+ if (skip) return this;
+ return GT_Utility.isArrayEmptyOrNull(outputs) ? this : invalidate();
+ }
+
+ /**
+ * Validate if output fluid match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateNoOutputFluid() {
+ if (skip) return this;
+ return GT_Utility.isArrayEmptyOrNull(fluidOutputs) ? this : invalidate();
+ }
+
+ /**
+ * Validate if input item match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateInputCount(int min, int max) {
+ if (skip) return this;
+ if (inputsBasic == null) return min < 0 ? this : invalidate();
+ return isArrayValid(inputsBasic, min, max) ? this : invalidate();
+ }
+
+ /**
+ * Validate if input fluid match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateInputFluidCount(int min, int max) {
+ if (skip) return this;
+ if (fluidInputs == null) return min < 0 ? this : invalidate();
+ return isArrayValid(fluidInputs, min, max) ? this : invalidate();
+ }
+
+ /**
+ * Validate if output item match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateOutputCount(int min, int max) {
+ if (skip) return this;
+ if (outputs == null) return min < 0 ? this : invalidate();
+ return isArrayValid(outputs, min, max) ? this : invalidate();
+ }
+
+ /**
+ * Validate if output fluid match requirement. Return as invalidated if fails prereq. Specify -1 as min to allow
+ * unset. Both bound inclusive. Only supposed to be called by IRecipeMap and not client code.
+ */
+ public GT_RecipeBuilder validateOutputFluidCount(int min, int max) {
+ if (skip) return this;
+ if (fluidOutputs == null) return min < 0 ? this : invalidate();
+ return isArrayValid(fluidOutputs, min, max) ? this : invalidate();
+ }
+
+ public GT_RecipeBuilder validateAnyInput() {
+ if (skip) return this;
+ if (fluidInputs != null && isArrayValid(fluidInputs, 1, Integer.MAX_VALUE)) {
+ return this;
+ }
+ if (inputsBasic != null && isArrayValid(inputsBasic, 1, Integer.MAX_VALUE)) {
+ return this;
+ }
+ return invalidate();
+ }
+
+ public GT_RecipeBuilder validateAnyOutput() {
+ if (skip) return this;
+ if (fluidOutputs != null && isArrayValid(fluidOutputs, 1, Integer.MAX_VALUE)) {
+ return this;
+ }
+ if (outputs != null && isArrayValid(outputs, 1, Integer.MAX_VALUE)) {
+ return this;
+ }
+ return invalidate();
+ }
+
+ // endregion
+
+ /**
+ * Builds new recipe, without custom behavior of recipemaps. For adding recipe to recipemap,
+ * use {@link #addTo} instead.
+ *
+ * @return Built recipe. Returns empty if failed to build.
+ */
+ public Optional<GT_Recipe> build() {
+ if (skip) {
+ return Optional.empty();
+ }
+ if (!valid) {
+ handleInvalidRecipe();
+ return Optional.empty();
+ }
+ preBuildChecks();
+ optimize();
+ return Optional.of(
+ decorate(
+ new GT_Recipe(
+ inputsBasic,
+ outputs,
+ fluidInputs,
+ fluidOutputs,
+ chances,
+ special,
+ duration,
+ eut,
+ specialValue,
+ enabled,
+ hidden,
+ fakeRecipe,
+ mCanBeBuffered,
+ mNeedsEmptyOutput,
+ nbtSensitive,
+ neiDesc,
+ metadataStorage,
+ recipeCategory)));
+ }
+
+ public GT_RecipeBuilder forceOreDictInput() {
+ if (inputsOreDict != null || inputsBasic == null) return this;
+ return itemInputs((Object[]) inputsBasic);
+ }
+
+ public Optional<GT_Recipe.GT_Recipe_WithAlt> buildWithAlt() {
+ if (skip) {
+ return Optional.empty();
+ }
+ if (inputsOreDict == null) {
+ throw new UnsupportedOperationException();
+ }
+ if (!valid) {
+ handleInvalidRecipe();
+ return Optional.empty();
+ }
+ preBuildChecks();
+ // no optimize.
+ return Optional.of(
+ decorate(
+ new GT_Recipe.GT_Recipe_WithAlt(
+ inputsBasic,
+ outputs,
+ fluidInputs,
+ fluidOutputs,
+ chances,
+ special,
+ duration,
+ eut,
+ specialValue,
+ enabled,
+ hidden,
+ fakeRecipe,
+ mCanBeBuffered,
+ mNeedsEmptyOutput,
+ nbtSensitive,
+ neiDesc,
+ metadataStorage,
+ recipeCategory,
+ alts)));
+ }
+
+ private void preBuildChecks() {
+ if (duration == -1) throw new IllegalStateException("no duration");
+ if (eut == -1) throw new IllegalStateException("no eut");
+ }
+
+ private void optimize() {
+ if (optimize) {
+ ArrayList<ItemStack> l = new ArrayList<>();
+ l.addAll(Arrays.asList(inputsBasic));
+ l.addAll(Arrays.asList(outputs));
+ for (int i = 0; i < l.size(); i++) if (l.get(i) == null) l.remove(i--);
+
+ outer: for (byte i = (byte) Math.min(64, duration / 16); i > 1; i--) {
+ if (duration / i >= 16) {
+ for (ItemStack stack : l) {
+ if (stack.stackSize % i != 0) continue outer;
+ }
+ for (FluidStack fluidInput : fluidInputs) {
+ if (fluidInput.amount % i != 0) continue outer;
+ }
+ for (FluidStack fluidOutput : fluidOutputs) {
+ if (fluidOutput.amount % i != 0) continue outer;
+ }
+ for (ItemStack itemStack : l) itemStack.stackSize /= i;
+ for (FluidStack fluidInput : fluidInputs) fluidInput.amount /= i;
+ for (FluidStack fluidOutput : fluidOutputs) fluidOutput.amount /= i;
+ duration /= i;
+ }
+ }
+ optimize = false;
+ }
+ }
+
+ private <T extends GT_Recipe> T decorate(T r) {
+ r.mHidden = hidden;
+ r.mCanBeBuffered = mCanBeBuffered;
+ r.mNeedsEmptyOutput = mNeedsEmptyOutput;
+ r.isNBTSensitive = nbtSensitive;
+ r.mFakeRecipe = fakeRecipe;
+ r.mEnabled = enabled;
+ if (neiDesc != null) r.setNeiDesc(neiDesc);
+ applyDefaultSpecialValues(r);
+ return r;
+ }
+
+ private void applyDefaultSpecialValues(GT_Recipe recipe) {
+ if (recipe.mSpecialValue != 0) return;
+
+ int specialValue = 0;
+ if (getMetadataOrDefault(GT_RecipeConstants.LOW_GRAVITY, false)) specialValue -= 100;
+ if (getMetadataOrDefault(GT_RecipeConstants.CLEANROOM, false)) specialValue -= 200;
+ for (RecipeMetadataKey<Integer> ident : SPECIAL_VALUE_ALIASES) {
+ Integer metadata = getMetadataOrDefault(ident, null);
+ if (metadata != null) {
+ specialValue = metadata;
+ break;
+ }
+ }
+ recipe.mSpecialValue = specialValue;
+ }
+
+ public Collection<GT_Recipe> addTo(IRecipeMap recipeMap) {
+ if (skip) {
+ return Collections.emptyList();
+ }
+ return recipeMap.doAdd(this);
+ }
+
+ public GT_RecipeBuilder reset() {
+ metadataStorage = null;
+ alts = null;
+ chances = null;
+ duration = -1;
+ enabled = true;
+ eut = -1;
+ fakeRecipe = false;
+ fluidInputs = null;
+ fluidOutputs = null;
+ hidden = false;
+ inputsBasic = null;
+ inputsOreDict = null;
+ mCanBeBuffered = true;
+ mNeedsEmptyOutput = false;
+ nbtSensitive = false;
+ neiDesc = null;
+ recipeCategory = null;
+ optimize = true;
+ outputs = null;
+ special = null;
+ specialValue = 0;
+ skip = false;
+ valid = true;
+ return this;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_RecipeConstants.java b/src/main/java/gregtech/api/util/GT_RecipeConstants.java
new file mode 100644
index 0000000000..d9d6c7d7d2
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_RecipeConstants.java
@@ -0,0 +1,332 @@
+package gregtech.api.util;
+
+import static gregtech.api.util.GT_RecipeMapUtil.convertCellToFluid;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Optional;
+
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.interfaces.IRecipeMap;
+import gregtech.api.recipe.RecipeCategories;
+import gregtech.api.recipe.RecipeMaps;
+import gregtech.api.recipe.RecipeMetadataKey;
+import gregtech.api.recipe.metadata.SimpleRecipeMetadataKey;
+
+// this class is intended to be import-static-ed on every recipe script
+// so take care to not put unrelated stuff here!
+public class GT_RecipeConstants {
+
+ /**
+ * Set to true to signal the recipe require low gravity. do nothing if recipe set specialValue explicitly. Can
+ * coexist with CLEANROOM just fine
+ */
+ public static final RecipeMetadataKey<Boolean> LOW_GRAVITY = SimpleRecipeMetadataKey
+ .create(Boolean.class, "low_gravity");
+ /**
+ * Set to true to signal the recipe require cleanroom. do nothing if recipe set specialValue explicitly. Can coexist
+ * with LOW_GRAVITY just fine
+ */
+ public static final RecipeMetadataKey<Boolean> CLEANROOM = SimpleRecipeMetadataKey
+ .create(Boolean.class, "cleanroom");
+ /**
+ * Common additive to use in recipe, e.g. for PBF, this is coal amount.
+ */
+ public static final RecipeMetadataKey<Integer> ADDITIVE_AMOUNT = SimpleRecipeMetadataKey
+ .create(Integer.class, "additives");
+ /**
+ * Used for fusion reactor. Denotes ignition threshold.
+ */
+ public static final RecipeMetadataKey<Integer> FUSION_THRESHOLD = SimpleRecipeMetadataKey
+ .create(Integer.class, "fusion_threshold");
+ /**
+ * Research time in a scanner used in ticks.
+ */
+ public static final RecipeMetadataKey<Integer> RESEARCH_TIME = SimpleRecipeMetadataKey
+ .create(Integer.class, "research_time");
+ /**
+ * Fuel type. TODO should we use enum directly?
+ */
+ public static final RecipeMetadataKey<Integer> FUEL_TYPE = SimpleRecipeMetadataKey
+ .create(Integer.class, "fuel_type");
+ /**
+ * Fuel value.
+ */
+ public static final RecipeMetadataKey<Integer> FUEL_VALUE = SimpleRecipeMetadataKey
+ .create(Integer.class, "fuel_value");
+ /**
+ * Required heat for heating coil (Kelvin).
+ */
+ public static final RecipeMetadataKey<Integer> COIL_HEAT = SimpleRecipeMetadataKey
+ .create(Integer.class, "coil_heat");
+ /**
+ * Research item used by assline recipes.
+ */
+ public static final RecipeMetadataKey<ItemStack> RESEARCH_ITEM = SimpleRecipeMetadataKey
+ .create(ItemStack.class, "research_item");
+ /**
+ * For assembler. It accepts a single item as oredict. It looks like no one uses this anyway...
+ */
+ public static final RecipeMetadataKey<Object> OREDICT_INPUT = SimpleRecipeMetadataKey
+ .create(Object.class, "oredict_input");
+ /**
+ * Replicator output material.
+ */
+ public static final RecipeMetadataKey<Materials> MATERIAL = SimpleRecipeMetadataKey
+ .create(Materials.class, "material");
+ /**
+ * Marker for {@link #UniversalArcFurnace} to tell that the recipe belongs to recycling category.
+ */
+ public static final RecipeMetadataKey<Boolean> RECYCLE = SimpleRecipeMetadataKey.create(Boolean.class, "recycle");
+ /**
+ * For Microwave.
+ */
+ public static final RecipeMetadataKey<Boolean> EXPLODE = SimpleRecipeMetadataKey.create(Boolean.class, "explode");
+ /**
+ * For Microwave.
+ */
+ public static final RecipeMetadataKey<Boolean> ON_FIRE = SimpleRecipeMetadataKey.create(Boolean.class, "on_fire");
+
+ /**
+ * Add a arc furnace recipe. Adds to both normal arc furnace and plasma arc furnace.
+ * Will override the fluid input with oxygen/plasma for the respective recipe maps, so there is no point setting it.
+ */
+ public static final IRecipeMap UniversalArcFurnace = IRecipeMap.newRecipeMap(builder -> {
+ if (!GT_Utility.isArrayOfLength(builder.getItemInputsBasic(), 1)
+ || GT_Utility.isArrayEmptyOrNull(builder.getItemOutputs())) return Collections.emptyList();
+ int aDuration = builder.getDuration();
+ if (aDuration <= 0) {
+ return Collections.emptyList();
+ }
+ builder.duration(aDuration);
+ boolean recycle = builder.getMetadataOrDefault(RECYCLE, false);
+ Collection<GT_Recipe> ret = new ArrayList<>();
+ for (Materials mat : new Materials[] { Materials.Argon, Materials.Nitrogen }) {
+ int tPlasmaAmount = (int) Math.max(1L, aDuration / (mat.getMass() * 16L));
+ GT_RecipeBuilder plasmaBuilder = builder.copy()
+ .fluidInputs(mat.getPlasma(tPlasmaAmount))
+ .fluidOutputs(mat.getGas(tPlasmaAmount));
+ if (recycle) {
+ plasmaBuilder.recipeCategory(RecipeCategories.plasmaArcFurnaceRecycling);
+ }
+ ret.addAll(RecipeMaps.plasmaArcFurnaceRecipes.doAdd(plasmaBuilder));
+ }
+ GT_RecipeBuilder arcBuilder = builder.copy()
+ .fluidInputs(Materials.Oxygen.getGas(aDuration));
+ if (recycle) {
+ arcBuilder.recipeCategory(RecipeCategories.arcFurnaceRecycling);
+ }
+ ret.addAll(RecipeMaps.arcFurnaceRecipes.doAdd(arcBuilder));
+ return ret;
+ });
+
+ /**
+ * Add a chemical reactor recipe to both LCR and singleblocks.
+ */
+ public static final IRecipeMap UniversalChemical = IRecipeMap.newRecipeMap(builder -> {
+ for (ItemStack input : builder.getItemInputsBasic()) {
+ // config >= 10 -> this is a special chemical recipe that output fluid/canned fluid variant.
+ // it doesn't belong to multiblocks
+ if (GT_Utility.isAnyIntegratedCircuit(input) && input.getItemDamage() >= 10) {
+ return builder.addTo(RecipeMaps.chemicalReactorRecipes);
+ }
+ }
+ return GT_Utility.concat(
+ builder.copy()
+ .addTo(RecipeMaps.chemicalReactorRecipes),
+ convertCellToFluid(builder, false)
+ // LCR does not need cleanroom.
+ .metadata(CLEANROOM, false)
+ .addTo(RecipeMaps.multiblockChemicalReactorRecipes));
+ });
+
+ /**
+ * The one and only :tm: assline recipe adder.
+ * Uses {@link #RESEARCH_ITEM} metadata as research item, and {@link #RESEARCH_TIME} metadata as research time, unit
+ * in ticks.
+ */
+ public static final IRecipeMap AssemblyLine = IRecipeMap.newRecipeMap(builder -> {
+ Optional<GT_Recipe.GT_Recipe_WithAlt> rr = builder.forceOreDictInput()
+ .validateInputCount(4, 16)
+ .validateOutputCount(1, 1)
+ .validateOutputFluidCount(-1, 0)
+ .validateInputFluidCount(0, 4)
+ .buildWithAlt();
+ // noinspection SimplifyOptionalCallChains
+ if (!rr.isPresent()) return Collections.emptyList();
+ GT_Recipe.GT_Recipe_WithAlt r = rr.get();
+ ItemStack[][] mOreDictAlt = r.mOreDictAlt;
+ Object[] inputs = builder.getItemInputsOreDict();
+ ItemStack aResearchItem = builder.getMetadata(RESEARCH_ITEM);
+ if (aResearchItem == null) {
+ return Collections.emptyList();
+ }
+ ItemStack aOutput = r.mOutputs[0];
+ int tPersistentHash = 1;
+ for (int i = 0, mOreDictAltLength = mOreDictAlt.length; i < mOreDictAltLength; i++) {
+ ItemStack[] alts = mOreDictAlt[i];
+ Object input = inputs[i];
+ if (input == null) {
+ GT_Log.err.println(
+ "addAssemblingLineRecipe " + aResearchItem.getDisplayName()
+ + " --> "
+ + aOutput.getUnlocalizedName()
+ + " there is some null item in that recipe");
+ }
+ if (input instanceof ItemStack) {
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash((ItemStack) input, true, false);
+ } else if (input instanceof ItemStack[]) {
+ for (ItemStack alt : ((ItemStack[]) input)) {
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(alt, true, false);
+ if (alt == null) {
+ GT_Log.err.println(
+ "addAssemblingLineRecipe " + aResearchItem.getDisplayName()
+ + " --> "
+ + aOutput.getUnlocalizedName()
+ + " there is some null alt item in that recipe");
+ }
+ }
+ tPersistentHash *= 31;
+ } else if (input instanceof Object[]objs) {
+ Arrays.sort(
+ alts,
+ Comparator
+ .<ItemStack, String>comparing(s -> GameRegistry.findUniqueIdentifierFor(s.getItem()).modId)
+ .thenComparing(s -> GameRegistry.findUniqueIdentifierFor(s.getItem()).name)
+ .thenComparingInt(Items.feather::getDamage)
+ .thenComparingInt(s -> s.stackSize));
+
+ tPersistentHash = tPersistentHash * 31 + (objs[0] == null ? "" : objs[0].toString()).hashCode();
+ tPersistentHash = tPersistentHash * 31 + ((Number) objs[1]).intValue();
+ }
+ }
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aResearchItem, true, false);
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aOutput, true, false);
+ for (FluidStack fluidInput : r.mFluidInputs) {
+ if (fluidInput == null) continue;
+ tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(fluidInput, true, false);
+ }
+ int aResearchTime = builder.getMetadataOrDefault(RESEARCH_TIME, 0);
+ tPersistentHash = tPersistentHash * 31 + aResearchTime;
+ tPersistentHash = tPersistentHash * 31 + r.mDuration;
+ tPersistentHash = tPersistentHash * 31 + r.mEUt;
+ Collection<GT_Recipe> ret = new ArrayList<>(3);
+ ret.add(
+ RecipeMaps.scannerFakeRecipes.addFakeRecipe(
+ false,
+ new ItemStack[] { aResearchItem },
+ new ItemStack[] { aOutput },
+ new ItemStack[] { ItemList.Tool_DataStick.getWithName(1L, "Writes Research result") },
+ null,
+ null,
+ aResearchTime,
+ 30,
+ -201)); // means it's scanned
+
+ ret.add(
+ RecipeMaps.assemblylineVisualRecipes.addFakeRecipe(
+ false,
+ r.mInputs,
+ new ItemStack[] { aOutput },
+ new ItemStack[] { ItemList.Tool_DataStick.getWithName(1L, "Reads Research result") },
+ r.mFluidInputs,
+ null,
+ r.mDuration,
+ r.mEUt,
+ 0,
+ r.mOreDictAlt,
+ false));
+
+ GT_Recipe.GT_Recipe_AssemblyLine tRecipe = new GT_Recipe.GT_Recipe_AssemblyLine(
+ aResearchItem,
+ aResearchTime,
+ r.mInputs,
+ r.mFluidInputs,
+ aOutput,
+ r.mDuration,
+ r.mEUt,
+ r.mOreDictAlt);
+ tRecipe.setPersistentHash(tPersistentHash);
+ GT_Recipe.GT_Recipe_AssemblyLine.sAssemblylineRecipes.add(tRecipe);
+ GT_AssemblyLineUtils.addRecipeToCache(tRecipe);
+ return ret;
+ });
+
+ /**
+ * Just like any normal assembler recipe, however it accepts one input item to be oredicted. Pass in the item to
+ * oredict via {@link #OREDICT_INPUT}. It will be used along all other item inputs as input of this recipe.
+ */
+ public static IRecipeMap AssemblerOD = IRecipeMap.newRecipeMap(builder -> {
+ Collection<GT_Recipe> ret = new ArrayList<>();
+ for (ItemStack input : GT_OreDictUnificator.getOresImmutable(builder.getMetadata(OREDICT_INPUT))) {
+ ret.addAll(
+ builder.copy()
+ .itemInputs(GT_RecipeMapUtil.appendArray(builder.getItemInputsBasic(), input))
+ .addTo(RecipeMaps.assemblerRecipes));
+ }
+ return ret;
+ });
+
+ /**
+ * A universal fuel adder. It's actually just a dispatcher towards all actual fuel recipe maps.
+ * Dispatch based on {@link #FUEL_TYPE}. Uses {@link #FUEL_VALUE} as fuel value.
+ * Can use {@link FuelType#ordinal()} as a human-readable form of what FUEL_TYPE should be.
+ * You can bypass this and add to relevant fuel maps directly if you wish.
+ */
+ public static IRecipeMap Fuel = IRecipeMap.newRecipeMap(builder -> {
+ builder.validateInputCount(1, 1)
+ .validateNoInputFluid()
+ .validateOutputCount(-1, 1)
+ .validateNoOutputFluid();
+ if (!builder.isValid()) return Collections.emptyList();
+ Integer fuelType = builder.getMetadata(FUEL_TYPE);
+ if (fuelType == null) return Collections.emptyList();
+ builder.metadata(FUEL_VALUE, builder.getMetadataOrDefault(FUEL_VALUE, 0));
+ return FuelType.get(fuelType)
+ .getTarget()
+ .doAdd(builder);
+ });
+
+ public enum FuelType {
+
+ // ORDER MATTERS. DO NOT INSERT ELEMENT BETWEEN EXISTING ONES
+ DieselFuel(RecipeMaps.dieselFuels),
+ GasTurbine(RecipeMaps.gasTurbineFuels),
+ // appears unused
+ HotFuel(RecipeMaps.hotFuels),
+ SemiFluid(RecipeMaps.denseLiquidFuels),
+ PlasmaTurbine(RecipeMaps.plasmaFuels),
+ Magic(RecipeMaps.magicFuels),;
+
+ private static final FuelType[] VALUES = values();
+ private final IRecipeMap target;
+
+ FuelType(IRecipeMap target) {
+ this.target = target;
+ }
+
+ public static FuelType get(int fuelType) {
+ if (fuelType < 0 || fuelType >= VALUES.length) return SemiFluid;
+ return VALUES[fuelType];
+ }
+
+ public IRecipeMap getTarget() {
+ return target;
+ }
+ }
+
+ static {
+ GT_RecipeMapUtil.SPECIAL_VALUE_ALIASES.add(COIL_HEAT);
+ GT_RecipeMapUtil.SPECIAL_VALUE_ALIASES.add(FUSION_THRESHOLD);
+ GT_RecipeMapUtil.SPECIAL_VALUE_ALIASES.add(FUEL_VALUE);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_RecipeMapUtil.java b/src/main/java/gregtech/api/util/GT_RecipeMapUtil.java
new file mode 100644
index 0000000000..3e97b56f84
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_RecipeMapUtil.java
@@ -0,0 +1,226 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.Mods.GregTech;
+import static gregtech.api.util.GT_Config.getStackConfigName;
+import static gregtech.api.util.GT_Utility.isArrayEmptyOrNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+
+import cpw.mods.fml.common.Loader;
+import gnu.trove.list.TIntList;
+import gnu.trove.list.array.TIntArrayList;
+import gregtech.api.interfaces.IRecipeMap;
+import gregtech.api.recipe.RecipeMetadataKey;
+
+/**
+ * Define helpers useful in the creation of recipe maps.
+ */
+public class GT_RecipeMapUtil {
+
+ public static final Function<GT_Recipe, GT_Recipe> ALL_FAKE_RECIPE = r -> {
+ r.mFakeRecipe = true;
+ return r;
+ };
+
+ public static final Function<GT_Recipe, String> FIRST_FLUID_INPUT = r -> isArrayEmptyOrNull(r.mFluidInputs) ? null
+ : r.mFluidInputs[0].getFluid()
+ .getName();
+ public static final Function<GT_Recipe, String> FIRST_FLUID_OUTPUT = r -> isArrayEmptyOrNull(r.mFluidInputs) ? null
+ : r.mFluidOutputs[0].getFluid()
+ .getName();
+ public static final Function<GT_Recipe, String> FIRST_FLUIDSTACK_INPUT = r -> isArrayEmptyOrNull(r.mFluidInputs)
+ ? null
+ : r.mFluidInputs[0].getUnlocalizedName();
+ public static final Function<GT_Recipe, String> FIRST_FLUIDSTACK_OUTPUT = r -> isArrayEmptyOrNull(r.mFluidOutputs)
+ ? null
+ : r.mFluidOutputs[0].getUnlocalizedName();
+ public static final Function<GT_Recipe, String> FIRST_ITEM_INPUT = r -> isArrayEmptyOrNull(r.mInputs) ? null
+ : getStackConfigName(r.mInputs[0]);
+ public static final Function<GT_Recipe, String> FIRST_ITEM_OUTPUT = r -> isArrayEmptyOrNull(r.mOutputs) ? null
+ : getStackConfigName(r.mOutputs[0]);
+ public static final Function<GT_Recipe, String> FIRST_ITEM_OR_FLUID_INPUT = r -> isArrayEmptyOrNull(r.mInputs)
+ ? isArrayEmptyOrNull(r.mFluidInputs) ? null
+ : r.mFluidInputs[0].getFluid()
+ .getName()
+ : getStackConfigName(r.mInputs[0]);
+ public static final Function<GT_Recipe, String> FIRST_ITEM_OR_FLUID_OUTPUT = r -> isArrayEmptyOrNull(r.mOutputs)
+ ? isArrayEmptyOrNull(r.mFluidOutputs) ? null
+ : r.mFluidOutputs[0].getFluid()
+ .getName()
+ : getStackConfigName(r.mOutputs[0]);
+ private static final Map<String, IRecipeMap> addonRecipeMaps = new HashMap<>();
+ private static final Multimap<String, Consumer<IRecipeMap>> delayedActions = ArrayListMultimap.create();
+
+ /**
+ * Set of metadata that work as alias for special values.
+ */
+ public static final Set<RecipeMetadataKey<Integer>> SPECIAL_VALUE_ALIASES = new HashSet<>();
+
+ public static <T> T[] appendArray(T[] arr, T val) {
+ T[] newArr = Arrays.copyOf(arr, arr.length + 1);
+ newArr[arr.length] = val;
+ return newArr;
+ }
+
+ public static GT_RecipeTemplate asTemplate(GT_Recipe r) {
+ return asTemplate(r, false);
+ }
+
+ public static GT_RecipeTemplate asTemplate(GT_Recipe r, boolean includeTemplate) {
+ return new GT_RecipeTemplate(r, includeTemplate);
+ }
+
+ public static List<GT_Recipe> buildRecipeForMultiblock(GT_RecipeBuilder b) {
+ return buildOrEmpty(convertCellToFluid(b, true));
+
+ }
+
+ public static List<GT_Recipe> buildRecipeForMultiblockNoCircuit(GT_RecipeBuilder b) {
+ return buildOrEmpty(convertCellToFluid(b, false));
+ }
+
+ public static GT_RecipeBuilder convertCellToFluid(GT_RecipeBuilder b, boolean removeIntegratedCircuit) {
+ List<ItemStack> itemInputs = new ArrayList<>(Arrays.asList(b.getItemInputsBasic()));
+ List<ItemStack> itemOutputs = new ArrayList<>(Arrays.asList(b.getItemOutputs()));
+ List<FluidStack> fluidInputs = new ArrayList<>(Arrays.asList(b.getFluidInputs()));
+ List<FluidStack> fluidOutputs = new ArrayList<>(Arrays.asList(b.getFluidOutputs()));
+ TIntList chances = b.getChances() != null ? new TIntArrayList(b.getChances()) : null;
+ cellToFluid(itemInputs, fluidInputs, removeIntegratedCircuit, null);
+ cellToFluid(itemOutputs, fluidOutputs, removeIntegratedCircuit, chances);
+ itemInputs.removeIf(Objects::isNull);
+ if (chances == null) {
+ itemOutputs.removeIf(Objects::isNull);
+ }
+ fluidInputs.removeIf(Objects::isNull);
+ fluidOutputs.removeIf(Objects::isNull);
+ b.itemInputs(itemInputs.toArray(new ItemStack[0]));
+ b.itemOutputs(itemOutputs.toArray(new ItemStack[0]), chances != null ? chances.toArray() : null);
+ b.fluidInputs(fluidInputs.toArray(new FluidStack[0]));
+ b.fluidOutputs(fluidOutputs.toArray(new FluidStack[0]));
+ return b;
+ }
+
+ private static void cellToFluid(List<ItemStack> items, List<FluidStack> fluids, boolean removeIntegratedCircuit,
+ TIntList chances) {
+ for (int i = items.size() - 1; i >= 0; i--) {
+ ItemStack item = items.get(i);
+ if (GT_Utility.getFluidForFilledItem(item, true) != null || GT_Utility.isCellEmpty(item)
+ || (removeIntegratedCircuit && GT_Utility.isAnyIntegratedCircuit(item))) {
+ fluids.add(GT_Utility.convertCellToFluid(item));
+ items.remove(i);
+ if (chances != null) chances.removeAt(i);
+ }
+ }
+ }
+
+ public static List<GT_Recipe> buildOrEmpty(GT_RecipeBuilder builder) {
+ return builder.build()
+ .map(Collections::singletonList)
+ .orElse(Collections.emptyList());
+ }
+
+ /**
+ * Register a recipe map as part of your mod's public API under your modID and your given identifier.
+ *
+ * @param identifier map name
+ * @param recipeMap the map to register
+ * @param dependencies fully qualified identifier of dependent recipe maps. scheduler will only add recipes to one
+ * of the dependent recipe maps and this recipe map concurrently, guaranteeing thread safety.
+ * Currently unused, but you are advised to fill them, so that when The Day (tm) comes we don't
+ * end up with a bunch of weird concurrency bugs.
+ */
+ public static void registerRecipeMap(String identifier, IRecipeMap recipeMap, RecipeMapDependency... dependencies) {
+ String modId = Loader.instance()
+ .activeModContainer()
+ .getModId();
+ if (GregTech.ID.equals(modId)) throw new IllegalStateException(
+ "do not register recipe map under the name of gregtech! do it in your own preinit!");
+ String id = modId + "@" + identifier;
+ addonRecipeMaps.put(id, recipeMap);
+ for (Consumer<IRecipeMap> action : delayedActions.get(id)) {
+ action.accept(recipeMap);
+ }
+ }
+
+ /**
+ * Use this to register recipes for a recipe map in addon not present at compile time.
+ * <p>
+ * Do not use this for recipes maps already in {@link GT_RecipeConstants}. None of them will be available via this
+ * interface!
+ *
+ * @param identifier recipe map id
+ * @param registerAction DO NOT ADD RECIPES TO MAPS OTHER THAN THE ONE PASSED TO YOU. DO NOT DO ANYTHING OTHER THAN
+ * ADDING RECIPES TO THIS R
+ */
+ public static void registerRecipesFor(String modid, String identifier, Consumer<IRecipeMap> registerAction) {
+ String id = modid + "@" + identifier;
+ IRecipeMap map = addonRecipeMaps.get(id);
+ if (map == null) delayedActions.put(id, registerAction);
+ else registerAction.accept(map);
+ }
+
+ public static final class GT_RecipeTemplate {
+
+ private final GT_Recipe template;
+ private final List<GT_Recipe> derivatives = new ArrayList<>();
+
+ private GT_RecipeTemplate(GT_Recipe template, boolean includeTemplate) {
+ this.template = template;
+ if (includeTemplate) derivatives.add(template);
+ }
+
+ public GT_Recipe derive() {
+ GT_Recipe derived = template.copyShallow();
+ derivatives.add(derived);
+ return derived;
+ }
+
+ public List<GT_Recipe> getAll() {
+ // fix shallow references
+ Set<Object> references = Collections.newSetFromMap(new IdentityHashMap<>());
+ for (GT_Recipe r : derivatives) {
+ if (!references.add(r.mInputs)) r.mInputs = r.mInputs.clone();
+ if (!references.add(r.mOutputs)) r.mOutputs = r.mOutputs.clone();
+ if (!references.add(r.mFluidInputs)) r.mFluidInputs = r.mFluidInputs.clone();
+ if (!references.add(r.mFluidOutputs)) r.mFluidOutputs = r.mFluidOutputs.clone();
+ }
+ return derivatives;
+ }
+ }
+
+ public static final class RecipeMapDependency {
+
+ private final IRecipeMap obj;
+ private final String id;
+
+ public RecipeMapDependency(IRecipeMap obj, String id) {
+ this.obj = obj;
+ this.id = id;
+ }
+
+ public static RecipeMapDependency create(String id) {
+ return new RecipeMapDependency(null, id);
+ }
+
+ public static RecipeMapDependency create(IRecipeMap obj) {
+ return new RecipeMapDependency(obj, null);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_RecipeRegistrator.java b/src/main/java/gregtech/api/util/GT_RecipeRegistrator.java
new file mode 100644
index 0000000000..f4490b59b0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_RecipeRegistrator.java
@@ -0,0 +1,868 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.GT_Values.L;
+import static gregtech.api.enums.GT_Values.M;
+import static gregtech.api.enums.GT_Values.RA;
+import static gregtech.api.enums.Materials.Bronze;
+import static gregtech.api.enums.Materials.Cobalt;
+import static gregtech.api.enums.Materials.DarkSteel;
+import static gregtech.api.enums.Materials.Diamond;
+import static gregtech.api.enums.Materials.FierySteel;
+import static gregtech.api.enums.Materials.Gold;
+import static gregtech.api.enums.Materials.Iron;
+import static gregtech.api.enums.Materials.IronWood;
+import static gregtech.api.enums.Materials.Knightmetal;
+import static gregtech.api.enums.Materials.Lead;
+import static gregtech.api.enums.Materials.Ruby;
+import static gregtech.api.enums.Materials.Sapphire;
+import static gregtech.api.enums.Materials.Steel;
+import static gregtech.api.enums.Materials.Steeleaf;
+import static gregtech.api.enums.Materials.Thaumium;
+import static gregtech.api.enums.Materials.Void;
+import static gregtech.api.recipe.RecipeMaps.fluidExtractionRecipes;
+import static gregtech.api.recipe.RecipeMaps.hammerRecipes;
+import static gregtech.api.recipe.RecipeMaps.maceratorRecipes;
+import static gregtech.api.recipe.RecipeMaps.wiremillRecipes;
+import static gregtech.api.util.GT_RecipeBuilder.SECONDS;
+import static gregtech.api.util.GT_RecipeBuilder.TICKS;
+import static gregtech.api.util.GT_RecipeConstants.RECYCLE;
+import static gregtech.api.util.GT_RecipeConstants.UniversalArcFurnace;
+import static gregtech.api.util.GT_Utility.calculateRecipeEU;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.CraftingManager;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.item.crafting.ShapedRecipes;
+import net.minecraft.item.crafting.ShapelessRecipes;
+import net.minecraftforge.oredict.ShapedOreRecipe;
+import net.minecraftforge.oredict.ShapelessOreRecipe;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.SetMultimap;
+
+import cpw.mods.fml.relauncher.ReflectionHelper;
+import gregtech.GT_Mod;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SubTag;
+import gregtech.api.enums.TierEU;
+import gregtech.api.objects.ItemData;
+import gregtech.api.objects.MaterialStack;
+import gregtech.api.recipe.RecipeCategories;
+import ic2.api.reactor.IReactorComponent;
+
+/**
+ * Class for Automatic Recipe registering.
+ */
+public class GT_RecipeRegistrator {
+
+ /**
+ * List of Materials, which are used in the Creation of Sticks. All Rod Materials are automatically added to this
+ * List.
+ */
+ public static final List<Materials> sRodMaterialList = new ArrayList<>();
+
+ private static final ItemStack sMt1 = new ItemStack(Blocks.dirt, 1, 0), sMt2 = new ItemStack(Blocks.dirt, 1, 0);
+ private static final String s_H = "h", s_F = "f", s_I = "I", s_P = "P", s_R = "R";
+ private static final RecipeShape[] sShapes = new RecipeShape[] {
+ new RecipeShape(sMt1, null, sMt1, sMt1, sMt1, sMt1, null, sMt1, null),
+ new RecipeShape(sMt1, null, sMt1, sMt1, null, sMt1, sMt1, sMt1, sMt1),
+ new RecipeShape(null, sMt1, null, sMt1, sMt1, sMt1, sMt1, null, sMt1),
+ new RecipeShape(sMt1, sMt1, sMt1, sMt1, null, sMt1, null, null, null),
+ new RecipeShape(sMt1, null, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1),
+ new RecipeShape(sMt1, sMt1, sMt1, sMt1, null, sMt1, sMt1, null, sMt1),
+ new RecipeShape(null, null, null, sMt1, null, sMt1, sMt1, null, sMt1),
+ new RecipeShape(null, sMt1, null, null, sMt1, null, null, sMt2, null),
+ new RecipeShape(sMt1, sMt1, sMt1, null, sMt2, null, null, sMt2, null),
+ new RecipeShape(null, sMt1, null, null, sMt2, null, null, sMt2, null),
+ new RecipeShape(sMt1, sMt1, null, sMt1, sMt2, null, null, sMt2, null),
+ new RecipeShape(null, sMt1, sMt1, null, sMt2, sMt1, null, sMt2, null),
+ new RecipeShape(sMt1, sMt1, null, null, sMt2, null, null, sMt2, null),
+ new RecipeShape(null, sMt1, sMt1, null, sMt2, null, null, sMt2, null),
+ new RecipeShape(null, sMt1, null, sMt1, null, null, null, sMt1, sMt2),
+ new RecipeShape(null, sMt1, null, null, null, sMt1, sMt2, sMt1, null),
+ new RecipeShape(null, sMt1, null, sMt1, null, sMt1, null, null, sMt2),
+ new RecipeShape(null, sMt1, null, sMt1, null, sMt1, sMt2, null, null),
+ new RecipeShape(null, sMt2, null, null, sMt1, null, null, sMt1, null),
+ new RecipeShape(null, sMt2, null, null, sMt2, null, sMt1, sMt1, sMt1),
+ new RecipeShape(null, sMt2, null, null, sMt2, null, null, sMt1, null),
+ new RecipeShape(null, sMt2, null, sMt1, sMt2, null, sMt1, sMt1, null),
+ new RecipeShape(null, sMt2, null, null, sMt2, sMt1, null, sMt1, sMt1),
+ new RecipeShape(null, sMt2, null, null, sMt2, null, sMt1, sMt1, null),
+ new RecipeShape(sMt1, null, null, null, sMt2, null, null, null, sMt2),
+ new RecipeShape(null, null, sMt1, null, sMt2, null, sMt2, null, null),
+ new RecipeShape(sMt1, null, null, null, sMt2, null, null, null, null),
+ new RecipeShape(null, null, sMt1, null, sMt2, null, null, null, null),
+ new RecipeShape(sMt1, sMt2, null, null, null, null, null, null, null),
+ new RecipeShape(sMt2, sMt1, null, null, null, null, null, null, null),
+ new RecipeShape(sMt1, null, null, sMt2, null, null, null, null, null),
+ new RecipeShape(sMt2, null, null, sMt1, null, null, null, null, null),
+ new RecipeShape(sMt1, sMt1, sMt1, sMt1, sMt1, sMt1, null, sMt2, null),
+ new RecipeShape(sMt1, sMt1, null, sMt1, sMt1, sMt2, sMt1, sMt1, null),
+ new RecipeShape(null, sMt1, sMt1, sMt2, sMt1, sMt1, null, sMt1, sMt1),
+ new RecipeShape(null, sMt2, null, sMt1, sMt1, sMt1, sMt1, sMt1, sMt1),
+ new RecipeShape(sMt1, sMt1, sMt1, sMt1, sMt2, sMt1, null, sMt2, null),
+ new RecipeShape(sMt1, sMt1, null, sMt1, sMt2, sMt2, sMt1, sMt1, null),
+ new RecipeShape(null, sMt1, sMt1, sMt2, sMt2, sMt1, null, sMt1, sMt1),
+ new RecipeShape(null, sMt2, null, sMt1, sMt2, sMt1, sMt1, sMt1, sMt1),
+ new RecipeShape(sMt1, null, null, null, sMt1, null, null, null, null),
+ new RecipeShape(null, sMt1, null, sMt1, null, null, null, null, null),
+ new RecipeShape(sMt1, sMt1, null, sMt2, null, sMt1, sMt2, null, null),
+ new RecipeShape(null, sMt1, sMt1, sMt1, null, sMt2, null, null, sMt2) };
+ public static final Field SHAPED_ORE_RECIPE_WIDTH = ReflectionHelper.findField(ShapedOreRecipe.class, "width");
+ public static final Field SHAPED_ORE_RECIPE_HEIGHT = ReflectionHelper.findField(ShapedOreRecipe.class, "height");
+ private static volatile Map<RecipeShape, List<IRecipe>> indexedRecipeListCache;
+ private static final String[][] sShapesA = new String[][] { null, null, null,
+ { "Helmet", s_P + s_P + s_P, s_P + s_H + s_P },
+ { "ChestPlate", s_P + s_H + s_P, s_P + s_P + s_P, s_P + s_P + s_P },
+ { "Pants", s_P + s_P + s_P, s_P + s_H + s_P, s_P + " " + s_P }, { "Boots", s_P + " " + s_P, s_P + s_H + s_P },
+ { "Sword", " " + s_P + " ", s_F + s_P + s_H, " " + s_R + " " },
+ { "Pickaxe", s_P + s_I + s_I, s_F + s_R + s_H, " " + s_R + " " },
+ { "Shovel", s_F + s_P + s_H, " " + s_R + " ", " " + s_R + " " },
+ { "Axe", s_P + s_I + s_H, s_P + s_R + " ", s_F + s_R + " " },
+ { "Axe", s_P + s_I + s_H, s_P + s_R + " ", s_F + s_R + " " },
+ { "Hoe", s_P + s_I + s_H, s_F + s_R + " ", " " + s_R + " " },
+ { "Hoe", s_P + s_I + s_H, s_F + s_R + " ", " " + s_R + " " },
+ { "Sickle", " " + s_P + " ", s_P + s_F + " ", s_H + s_P + s_R },
+ { "Sickle", " " + s_P + " ", s_P + s_F + " ", s_H + s_P + s_R },
+ { "Sickle", " " + s_P + " ", s_P + s_F + " ", s_H + s_P + s_R },
+ { "Sickle", " " + s_P + " ", s_P + s_F + " ", s_H + s_P + s_R },
+ { "Sword", " " + s_R + " ", s_F + s_P + s_H, " " + s_P + " " },
+ { "Pickaxe", " " + s_R + " ", s_F + s_R + s_H, s_P + s_I + s_I },
+ { "Shovel", " " + s_R + " ", " " + s_R + " ", s_F + s_P + s_H },
+ { "Axe", s_F + s_R + " ", s_P + s_R + " ", s_P + s_I + s_H },
+ { "Axe", s_F + s_R + " ", s_P + s_R + " ", s_P + s_I + s_H },
+ { "Hoe", " " + s_R + " ", s_F + s_R + " ", s_P + s_I + s_H },
+ { "Hoe", " " + s_R + " ", s_F + s_R + " ", s_P + s_I + s_H },
+ { "Spear", s_P + s_H + " ", s_F + s_R + " ", " " + " " + s_R },
+ { "Spear", s_P + s_H + " ", s_F + s_R + " ", " " + " " + s_R }, { "Knive", s_H + s_P, s_R + s_F },
+ { "Knive", s_F + s_H, s_P + s_R }, { "Knive", s_F + s_H, s_P + s_R }, { "Knive", s_P + s_F, s_R + s_H },
+ { "Knive", s_P + s_F, s_R + s_H }, null, null, null, null,
+ { "WarAxe", s_P + s_P + s_P, s_P + s_R + s_P, s_F + s_R + s_H }, null, null, null,
+ { "Shears", s_H + s_P, s_P + s_F }, { "Shears", s_H + s_P, s_P + s_F },
+ { "Scythe", s_I + s_P + s_H, s_R + s_F + s_P, s_R + " " + " " },
+ { "Scythe", s_H + s_P + s_I, s_P + s_F + s_R, " " + " " + s_R } };
+
+ static {
+ // flush the cache on post load finish
+ GregTech_API.sAfterGTPostload.add(() -> indexedRecipeListCache = null);
+ }
+
+ public static void registerMaterialRecycling(ItemStack aStack, Materials aMaterial, long aMaterialAmount,
+ MaterialStack aByproduct) {
+ if (GT_Utility.isStackInvalid(aStack)) return;
+ if (aByproduct != null) {
+ aByproduct = aByproduct.clone();
+ aByproduct.mAmount /= aStack.stackSize;
+ }
+ GT_OreDictUnificator.addItemData(
+ GT_Utility.copyAmount(1, aStack),
+ new ItemData(aMaterial, aMaterialAmount / aStack.stackSize, aByproduct));
+ }
+
+ public static void registerMaterialRecycling(ItemStack aStack, ItemData aData) {
+ if (GT_Utility.isStackInvalid(aStack) || GT_Utility.areStacksEqual(new ItemStack(Items.blaze_rod), aStack)
+ || aData == null
+ || !aData.hasValidMaterialData()
+ || !aData.mMaterial.mMaterial.mAutoGenerateRecycleRecipes
+ || aData.mMaterial.mAmount <= 0
+ || GT_Utility.getFluidForFilledItem(aStack, false) != null
+ || aData.mMaterial.mMaterial.mSubTags.contains(SubTag.NO_RECIPES)) return;
+ registerReverseMacerating(GT_Utility.copyAmount(1, aStack), aData, aData.mPrefix == null);
+ if (!GT_Utility.areStacksEqual(GT_ModHandler.getIC2Item("iridiumOre", 1L), aStack)) {
+ registerReverseSmelting(
+ GT_Utility.copyAmount(1, aStack),
+ aData.mMaterial.mMaterial,
+ aData.mMaterial.mAmount,
+ true);
+ registerReverseFluidSmelting(
+ GT_Utility.copyAmount(1, aStack),
+ aData.mMaterial.mMaterial,
+ aData.mMaterial.mAmount,
+ aData.getByProduct(0));
+ registerReverseArcSmelting(GT_Utility.copyAmount(1, aStack), aData);
+ }
+ }
+
+ /**
+ * @param aStack the stack to be recycled.
+ * @param aMaterial the Material.
+ * @param aMaterialAmount the amount of it in Material Units.
+ */
+ public static void registerReverseFluidSmelting(ItemStack aStack, Materials aMaterial, long aMaterialAmount,
+ MaterialStack aByproduct) {
+ if (aStack == null || aMaterial == null
+ || aMaterial.mSmeltInto.mStandardMoltenFluid == null
+ || !aMaterial.contains(SubTag.SMELTING_TO_FLUID)
+ || (L * aMaterialAmount) / (M * aStack.stackSize) <= 0) return;
+
+ ItemStack recipeOutput = aByproduct == null ? null
+ : aByproduct.mMaterial.contains(SubTag.NO_SMELTING) || !aByproduct.mMaterial.contains(SubTag.METAL)
+ ? aByproduct.mMaterial.contains(SubTag.FLAMMABLE)
+ ? GT_OreDictUnificator.getDust(Materials.Ash, aByproduct.mAmount / 2)
+ : aByproduct.mMaterial.contains(SubTag.UNBURNABLE)
+ ? GT_OreDictUnificator.getDustOrIngot(aByproduct.mMaterial.mSmeltInto, aByproduct.mAmount)
+ : null
+ : GT_OreDictUnificator.getIngotOrDust(aByproduct.mMaterial.mSmeltInto, aByproduct.mAmount);
+
+ GT_RecipeBuilder builder = RA.stdBuilder()
+ .itemInputs(GT_Utility.copyAmount(1, aStack));
+ if (recipeOutput != null) {
+ builder.itemOutputs(recipeOutput);
+ }
+ builder.fluidOutputs(aMaterial.mSmeltInto.getMolten((L * aMaterialAmount) / (M * aStack.stackSize)))
+ .duration((int) Math.max(1, (24 * aMaterialAmount) / M))
+ .eut(Math.max(8, (int) Math.sqrt(2 * aMaterial.mSmeltInto.mStandardMoltenFluid.getTemperature())))
+ .recipeCategory(RecipeCategories.fluidExtractorRecycling)
+ .addTo(fluidExtractionRecipes);
+ }
+
+ /**
+ * @param aStack the stack to be recycled.
+ * @param aMaterial the Material.
+ * @param aMaterialAmount the amount of it in Material Units.
+ * @param aAllowAlloySmelter if it is allowed to be recycled inside the Alloy Smelter.
+ */
+ public static void registerReverseSmelting(ItemStack aStack, Materials aMaterial, long aMaterialAmount,
+ boolean aAllowAlloySmelter) {
+ if (aStack == null || aMaterial == null
+ || aMaterialAmount <= 0
+ || aMaterial.contains(SubTag.NO_SMELTING)
+ || (aMaterialAmount > M && aMaterial.contains(SubTag.METAL))
+ || (aMaterial.getProcessingMaterialTierEU() > TierEU.IV)) return;
+ if (aMaterial == Materials.Naquadah || aMaterial == Materials.NaquadahEnriched) return;
+
+ aMaterialAmount /= aStack.stackSize;
+
+ if (aAllowAlloySmelter) GT_ModHandler.addSmeltingAndAlloySmeltingRecipe(
+ GT_Utility.copyAmount(1, aStack),
+ GT_OreDictUnificator.getIngot(aMaterial.mSmeltInto, aMaterialAmount),
+ false);
+ else GT_ModHandler.addSmeltingRecipe(
+ GT_Utility.copyAmount(1, aStack),
+ GT_OreDictUnificator.getIngot(aMaterial.mSmeltInto, aMaterialAmount));
+ }
+
+ public static void registerReverseArcSmelting(ItemStack aStack, Materials aMaterial, long aMaterialAmount,
+ MaterialStack aByProduct01, MaterialStack aByProduct02, MaterialStack aByProduct03) {
+ registerReverseArcSmelting(
+ aStack,
+ new ItemData(
+ aMaterial == null ? null : new MaterialStack(aMaterial, aMaterialAmount),
+ aByProduct01,
+ aByProduct02,
+ aByProduct03));
+ }
+
+ public static void registerReverseArcSmelting(ItemStack aStack, ItemData aData) {
+ if (aStack == null || aData == null) return;
+ aData = new ItemData(aData);
+
+ if (!aData.hasValidMaterialData()) return;
+ boolean isRecycle = true;
+
+ for (MaterialStack tMaterial : aData.getAllMaterialStacks()) {
+ if (tMaterial.mMaterial == Materials.Iron || tMaterial.mMaterial == Materials.Copper
+ || tMaterial.mMaterial == Materials.WroughtIron
+ || tMaterial.mMaterial == Materials.AnnealedCopper) {
+ ItemData stackData = GT_OreDictUnificator.getItemData(aStack);
+ if (stackData != null
+ && (stackData.mPrefix == OrePrefixes.ingot || stackData.mPrefix == OrePrefixes.dust)) {
+ // iron ingot/dust -> wrought iron, copper ingot/dust -> annealed copper
+ isRecycle = false;
+ }
+ }
+
+ if (tMaterial.mMaterial.contains(SubTag.UNBURNABLE)) {
+ tMaterial.mMaterial = tMaterial.mMaterial.mSmeltInto.mArcSmeltInto;
+ continue;
+ }
+ if (tMaterial.mMaterial.contains(SubTag.EXPLOSIVE)) {
+ tMaterial.mMaterial = Materials.Ash;
+ tMaterial.mAmount /= 16;
+ continue;
+ }
+ if (tMaterial.mMaterial.contains(SubTag.FLAMMABLE)) {
+ tMaterial.mMaterial = Materials.Ash;
+ tMaterial.mAmount /= 8;
+ continue;
+ }
+ if (tMaterial.mMaterial.contains(SubTag.NO_SMELTING)) {
+ tMaterial.mAmount = 0;
+ continue;
+ }
+ if (tMaterial.mMaterial.contains(SubTag.METAL)) {
+ if (GT_Mod.gregtechproxy.mArcSmeltIntoAnnealed) {
+ tMaterial.mMaterial = tMaterial.mMaterial.mSmeltInto.mArcSmeltInto;
+ } else {
+ tMaterial.mMaterial = tMaterial.mMaterial.mSmeltInto.mSmeltInto;
+ }
+ continue;
+ }
+ tMaterial.mAmount = 0;
+ }
+
+ aData = new ItemData(aData);
+ if (aData.mByProducts.length > 3) for (MaterialStack tMaterial : aData.getAllMaterialStacks()) {
+ if (tMaterial.mMaterial == Materials.Ash) tMaterial.mAmount = 0;
+ }
+
+ aData = new ItemData(aData);
+
+ if (!aData.hasValidMaterialData()) return;
+
+ long tAmount = 0;
+ for (MaterialStack tMaterial : aData.getAllMaterialStacks())
+ tAmount += tMaterial.mAmount * tMaterial.mMaterial.getMass();
+
+ ArrayList<ItemStack> outputs = new ArrayList<>();
+ if (GT_OreDictUnificator.getIngotOrDust(aData.mMaterial) != null) {
+ outputs.add(GT_OreDictUnificator.getIngotOrDust(aData.mMaterial));
+ }
+ for (int i = 0; i < 8; i++) {
+ if (GT_OreDictUnificator.getIngotOrDust(aData.getByProduct(i)) != null) {
+ outputs.add(GT_OreDictUnificator.getIngotOrDust(aData.getByProduct(i)));
+ }
+ }
+ if (!outputs.isEmpty()) {
+ GT_RecipeBuilder recipeBuilder = GT_Values.RA.stdBuilder();
+ recipeBuilder.itemInputs(aStack)
+ .itemOutputs(outputs.toArray(new ItemStack[0]))
+ .fluidInputs(Materials.Oxygen.getGas((int) Math.max(16, tAmount / M)))
+ .duration(((int) Math.max(16, tAmount / M)) * TICKS)
+ .eut(90)
+ .metadata(RECYCLE, isRecycle)
+ .addTo(UniversalArcFurnace);
+ }
+
+ }
+
+ public static void registerReverseMacerating(ItemStack aStack, Materials aMaterial, long aMaterialAmount,
+ MaterialStack aByProduct01, MaterialStack aByProduct02, MaterialStack aByProduct03, boolean aAllowHammer) {
+ registerReverseMacerating(
+ aStack,
+ new ItemData(
+ aMaterial == null ? null : new MaterialStack(aMaterial, aMaterialAmount),
+ aByProduct01,
+ aByProduct02,
+ aByProduct03),
+ aAllowHammer);
+ }
+
+ public static void registerReverseMacerating(ItemStack aStack, ItemData aData, boolean aAllowHammer) {
+ if (aStack == null || aData == null) return;
+ aData = new ItemData(aData);
+
+ if (!aData.hasValidMaterialData()) return;
+
+ for (MaterialStack tMaterial : aData.getAllMaterialStacks())
+ tMaterial.mMaterial = tMaterial.mMaterial.mMacerateInto;
+
+ aData = new ItemData(aData);
+
+ if (!aData.hasValidMaterialData()) return;
+
+ long tAmount = 0;
+ for (MaterialStack tMaterial : aData.getAllMaterialStacks()) {
+ tAmount += tMaterial.mAmount * tMaterial.mMaterial.getMass();
+ }
+
+ {
+ ArrayList<ItemStack> outputs = new ArrayList<>();
+ if (GT_OreDictUnificator.getDust(aData.mMaterial) != null) {
+ outputs.add(GT_OreDictUnificator.getDust(aData.mMaterial));
+ }
+ for (int i = 0; i < 3; i++) {
+ if (GT_OreDictUnificator.getDust(aData.getByProduct(i)) != null) {
+ outputs.add(GT_OreDictUnificator.getDust(aData.getByProduct(i)));
+ }
+ }
+ if (!outputs.isEmpty()) {
+ ItemStack[] outputsArray = outputs.toArray(new ItemStack[0]);
+ GT_RecipeBuilder recipeBuilder = GT_Values.RA.stdBuilder();
+ recipeBuilder.itemInputs(aStack)
+ .itemOutputs(outputsArray)
+ .duration(
+ (aData.mMaterial.mMaterial == Materials.Marble ? 1 : (int) Math.max(16, tAmount / M)) * TICKS)
+ .eut(4)
+ .recipeCategory(RecipeCategories.maceratorRecycling)
+ .addTo(maceratorRecipes);
+ }
+ }
+
+ if (!aAllowHammer) {
+ return;
+ }
+
+ for (MaterialStack tMaterial : aData.getAllMaterialStacks()) {
+ if (tMaterial.mMaterial.contains(SubTag.CRYSTAL) && !tMaterial.mMaterial.contains(SubTag.METAL)
+ && tMaterial.mMaterial != Materials.Glass
+ && GT_OreDictUnificator.getDust(aData.mMaterial) != null) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GT_Utility.copyAmount(1, aStack))
+ .itemOutputs(GT_OreDictUnificator.getDust(aData.mMaterial))
+ .duration(10 * SECONDS)
+ .eut(TierEU.RECIPE_LV)
+ .recipeCategory(RecipeCategories.forgeHammerRecycling)
+ .addTo(hammerRecipes);
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * Place Materials which you want to replace in Non-GT-Recipes here (warning HUGHE impact on loading times!)
+ */
+ private static final Materials[] VANILLA_MATS = { Cobalt, Gold, Iron, Lead, FierySteel, Void, Bronze, Diamond, Ruby,
+ Sapphire, Steel, IronWood, Steeleaf, Knightmetal, Thaumium, DarkSteel, };
+
+ /**
+ * You give this Function a Material and it will scan almost everything for adding recycling Recipes and replacing
+ * Ingots, Gems etc.
+ *
+ * @param aMats Materials, for example an Ingot or a Gem.
+ * @param aPlate the Plate referenced to aMat
+ * @param aRecipeReplacing allows to replace the Recipe with a Plate variant
+ */
+ public static synchronized void registerUsagesForMaterials(String aPlate, boolean aRecipeReplacing,
+ ItemStack... aMats) {
+ for (ItemStack aMat : aMats) {
+ aMat = GT_Utility.copyOrNull(aMat);
+
+ if (aMat == null) continue;
+
+ ItemData aItemData = GT_OreDictUnificator.getItemData(aMat);
+ if (aItemData == null || aItemData.mPrefix != OrePrefixes.ingot) aPlate = null;
+ if (aPlate != null && GT_OreDictUnificator.getFirstOre(aPlate, 1) == null) aPlate = null;
+
+ sMt1.func_150996_a(aMat.getItem());
+ sMt1.stackSize = 1;
+ Items.feather.setDamage(sMt1, Items.feather.getDamage(aMat));
+
+ sMt2.func_150996_a(new ItemStack(Blocks.dirt).getItem());
+ sMt2.stackSize = 1;
+ Items.feather.setDamage(sMt2, 0);
+
+ if (aItemData != null && aItemData.hasValidPrefixMaterialData()) {
+ for (RecipeShape tRecipe : sShapes) {
+ for (ItemStack tCrafted : GT_ModHandler.getRecipeOutputsBuffered(tRecipe.shape)) {
+ GT_OreDictUnificator.addItemData(
+ tCrafted,
+ new ItemData(aItemData.mMaterial.mMaterial, aItemData.mMaterial.mAmount * tRecipe.amount1));
+ //
+ // GT_Log.out.println("###################################################################################");
+ // GT_Log.out.println("registerUsagesForMaterials used aPlate: "+aPlate);
+ // GT_Log.out.println("registerUsagesForMaterials used aPlate:
+ // "+aMat.getUnlocalizedName());
+ // GT_Log.out.println("registerUsagesForMaterials used aPlate:
+ // "+aMat.getDisplayName());
+ //
+ // GT_Log.out.println("###################################################################################");
+ }
+ }
+ }
+ registerStickStuff(aPlate, aItemData, aRecipeReplacing);
+ }
+ }
+
+ private static List<IRecipe> getRecipeList(RecipeShape shape) {
+ boolean force = !GregTech_API.sPostloadStarted || GregTech_API.sPostloadFinished;
+ if (force || indexedRecipeListCache == null) {
+ synchronized (GT_RecipeRegistrator.class) {
+ if (indexedRecipeListCache == null || force) {
+ indexedRecipeListCache = createIndexedRecipeListCache();
+ }
+ }
+ }
+ return indexedRecipeListCache.get(shape);
+ }
+
+ private static Map<RecipeShape, List<IRecipe>> createIndexedRecipeListCache() {
+ Map<RecipeShape, List<IRecipe>> result = new IdentityHashMap<>();
+ ArrayList<IRecipe> allRecipeList = (ArrayList<IRecipe>) CraftingManager.getInstance()
+ .getRecipeList();
+ // filter using the empty slots in the shape.
+ // if the empty slots doesn't match, the recipe will definitely fail
+ SetMultimap<List<Integer>, RecipeShape> filter = HashMultimap.create();
+ for (RecipeShape shape : sShapes) {
+ for (List<Integer> list : shape.getEmptySlotsAllVariants()) {
+ filter.put(list, shape);
+ }
+ }
+ List<Integer> buffer = new ArrayList<>(9);
+ for (IRecipe tRecipe : allRecipeList) {
+ if (tRecipe instanceof ShapelessRecipes || tRecipe instanceof ShapelessOreRecipe) {
+ // we don't target shapeless recipes
+ continue;
+ }
+ buffer.clear();
+ ItemStack tStack = tRecipe.getRecipeOutput();
+ if (GT_Utility.isStackValid(tStack) && tStack.getMaxStackSize() == 1
+ && tStack.getMaxDamage() > 0
+ && !(tStack.getItem() instanceof ItemBlock)
+ && !(tStack.getItem() instanceof IReactorComponent)
+ && !GT_ModHandler.isElectricItem(tStack)
+ && !GT_Utility.isStackInList(tStack, GT_ModHandler.sNonReplaceableItems)) {
+ if (tRecipe instanceof ShapedOreRecipe tShapedRecipe) {
+ if (checkRecipeShape(
+ buffer,
+ tShapedRecipe.getInput(),
+ getRecipeWidth(tShapedRecipe),
+ getRecipeHeight(tShapedRecipe))) {
+ for (RecipeShape s : filter.get(buffer)) {
+ result.computeIfAbsent(s, k -> new ArrayList<>())
+ .add(tRecipe);
+ }
+ }
+ } else if (tRecipe instanceof ShapedRecipes tShapedRecipe) {
+ if (checkRecipeShape(
+ buffer,
+ tShapedRecipe.recipeItems,
+ getRecipeWidth(tShapedRecipe),
+ getRecipeHeight(tShapedRecipe))) {
+ for (RecipeShape s : filter.get(buffer)) {
+ result.computeIfAbsent(s, k -> new ArrayList<>())
+ .add(tRecipe);
+ }
+ }
+ } else {
+ for (RecipeShape s : sShapes) {
+ // unknown recipe type. cannot determine empty slots. we choose to add to the recipe list for
+ // all shapes
+ result.computeIfAbsent(s, k -> new ArrayList<>())
+ .add(tRecipe);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private static boolean checkRecipeShape(List<Integer> emptySlotIndexesBuffer, Object[] input, int tRecipeWidth,
+ int tRecipeHeight) {
+ for (int y = 0; y < 3; y++) {
+ for (int x = 0; x < 3; x++) {
+ if (x >= tRecipeWidth || y >= tRecipeHeight) {
+ emptySlotIndexesBuffer.add(x + y * 3);
+ continue;
+ }
+ Object tObject = input[x + y * tRecipeWidth];
+ if (tObject == null) {
+ emptySlotIndexesBuffer.add(x + y * 3);
+ continue;
+ }
+ if (tObject instanceof ItemStack
+ && (((ItemStack) tObject).getItem() == null || ((ItemStack) tObject).getMaxStackSize() < 2
+ || ((ItemStack) tObject).getMaxDamage() > 0
+ || ((ItemStack) tObject).getItem() instanceof ItemBlock)) {
+ return false;
+ }
+ if (tObject instanceof List && ((List<?>) tObject).isEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static synchronized void registerStickStuff(String aPlate, ItemData aItemData, boolean aRecipeReplacing) {
+ ItemStack tStack;
+ for (Materials tMaterial : sRodMaterialList) {
+ ItemStack tMt2 = GT_OreDictUnificator.get(OrePrefixes.stick, tMaterial, 1);
+ if (tMt2 != null) {
+ sMt2.func_150996_a(tMt2.getItem());
+ sMt2.stackSize = 1;
+ Items.feather.setDamage(sMt2, Items.feather.getDamage(tMt2));
+
+ for (int i = 0; i < sShapes.length; i++) {
+ RecipeShape tRecipe = sShapes[i];
+
+ for (ItemStack tCrafted : GT_ModHandler
+ .getRecipeOutputs(getRecipeList(tRecipe), true, tRecipe.shape)) {
+ if (aItemData != null && aItemData.hasValidPrefixMaterialData())
+ GT_OreDictUnificator.addItemData(
+ tCrafted,
+ new ItemData(
+ aItemData.mMaterial.mMaterial,
+ aItemData.mMaterial.mAmount * tRecipe.amount1,
+ new MaterialStack(tMaterial, OrePrefixes.stick.mMaterialAmount * tRecipe.amount2)));
+
+ if (aRecipeReplacing && aPlate != null && sShapesA[i] != null && sShapesA[i].length > 1) {
+ assert aItemData != null;
+
+ if (null != (tStack = GT_ModHandler.removeRecipe(tRecipe.shape))) {
+ switch (sShapesA[i].length) {
+ case 2 -> GT_ModHandler.addCraftingRecipe(
+ tStack,
+ GT_ModHandler.RecipeBits.BUFFERED,
+ new Object[] { sShapesA[i][1], s_P.charAt(0), aPlate, s_R.charAt(0),
+ OrePrefixes.stick.get(tMaterial), s_I.charAt(0), aItemData });
+ case 3 -> GT_ModHandler.addCraftingRecipe(
+ tStack,
+ GT_ModHandler.RecipeBits.BUFFERED,
+ new Object[] { sShapesA[i][1], sShapesA[i][2], s_P.charAt(0), aPlate,
+ s_R.charAt(0), OrePrefixes.stick.get(tMaterial), s_I.charAt(0),
+ aItemData });
+ default -> GT_ModHandler.addCraftingRecipe(
+ tStack,
+ GT_ModHandler.RecipeBits.BUFFERED,
+ new Object[] { sShapesA[i][1], sShapesA[i][2], sShapesA[i][3], s_P.charAt(0),
+ aPlate, s_R.charAt(0), OrePrefixes.stick.get(tMaterial), s_I.charAt(0),
+ aItemData });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Registers wiremill recipes for given material using integrated circuits.
+ *
+ * @param aMaterial material to register
+ * @param baseDuration base duration ticks for ingot -> 1x wire recipe
+ * @param aEUt EU/t for recipe If you provide a proper EU tier for recipe processing then aEUt will be
+ * overriden with it.
+ */
+ public static void registerWiremillRecipes(Materials aMaterial, int baseDuration, int aEUt) {
+ registerWiremillRecipes(
+ aMaterial,
+ baseDuration,
+ calculateRecipeEU(aMaterial, aEUt),
+ OrePrefixes.ingot,
+ OrePrefixes.stick,
+ 2);
+ }
+
+ /**
+ * Registers wiremill recipes for given material using integrated circuits.
+ *
+ * @param aMaterial material to register
+ * @param baseDuration base duration ticks for ingot -> 1x wire recipe
+ * @param aEUt EU/t for recipe
+ * @param prefix1 prefix corresponds to ingot
+ * @param prefix2 prefix corresponds to stick
+ * @param multiplier amount of wires created from 1 ingot
+ */
+ public static void registerWiremillRecipes(Materials aMaterial, int baseDuration, int aEUt, OrePrefixes prefix1,
+ OrePrefixes prefix2, int multiplier) {
+ if (GT_OreDictUnificator.get(prefix1, aMaterial, 1L) != null
+ && GT_OreDictUnificator.get(OrePrefixes.wireGt01, aMaterial, 1L) != null) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GT_OreDictUnificator.get(prefix1, aMaterial, 1L), GT_Utility.getIntegratedCircuit(1))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt01, aMaterial, multiplier))
+ .duration(baseDuration * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix1, aMaterial, 2L / multiplier),
+ GT_Utility.getIntegratedCircuit(2))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt02, aMaterial, 1L))
+ .duration(((int) (baseDuration * 1.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix1, aMaterial, 4L / multiplier),
+ GT_Utility.getIntegratedCircuit(4))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt04, aMaterial, 1L))
+ .duration(baseDuration * 2 * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix1, aMaterial, 8L / multiplier),
+ GT_Utility.getIntegratedCircuit(8))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt08, aMaterial, 1L))
+ .duration(((int) (baseDuration * 2.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix1, aMaterial, 12L / multiplier),
+ GT_Utility.getIntegratedCircuit(12))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt12, aMaterial, 1L))
+ .duration(baseDuration * 3 * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix1, aMaterial, 16L / multiplier),
+ GT_Utility.getIntegratedCircuit(16))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt16, aMaterial, 1L))
+ .duration(((int) (baseDuration * 3.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ }
+
+ if (GT_OreDictUnificator.get(prefix2, aMaterial, 1L) != null
+ && GT_OreDictUnificator.get(OrePrefixes.wireGt01, aMaterial, 1L) != null) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GT_OreDictUnificator.get(prefix2, aMaterial, 1L), GT_Utility.getIntegratedCircuit(1))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt01, aMaterial, 2L / multiplier))
+ .duration(((int) (baseDuration * 0.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix2, aMaterial, 4L / multiplier),
+ GT_Utility.getIntegratedCircuit(2))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt02, aMaterial, 1L))
+ .duration(baseDuration * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix2, aMaterial, 8L / multiplier),
+ GT_Utility.getIntegratedCircuit(4))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt04, aMaterial, 1L))
+ .duration(((int) (baseDuration * 1.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix2, aMaterial, 16L / multiplier),
+ GT_Utility.getIntegratedCircuit(8))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt08, aMaterial, 1L))
+ .duration(baseDuration * 2 * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix2, aMaterial, 24L / multiplier),
+ GT_Utility.getIntegratedCircuit(12))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt12, aMaterial, 1L))
+ .duration(((int) (baseDuration * 2.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ GT_Values.RA.stdBuilder()
+ .itemInputs(
+ GT_OreDictUnificator.get(prefix2, aMaterial, 32L / multiplier),
+ GT_Utility.getIntegratedCircuit(16))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireGt16, aMaterial, 1L))
+ .duration(baseDuration * 3 * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ }
+ if (GT_OreDictUnificator.get(prefix1, aMaterial, 1L) != null
+ && GT_OreDictUnificator.get(OrePrefixes.wireFine, aMaterial, 1L) != null) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GT_OreDictUnificator.get(prefix1, aMaterial, 1L), GT_Utility.getIntegratedCircuit(3))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireFine, aMaterial, 4L * multiplier))
+ .duration(baseDuration * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ }
+ if (GT_OreDictUnificator.get(prefix2, aMaterial, 1L) != null
+ && GT_OreDictUnificator.get(OrePrefixes.wireFine, aMaterial, 1L) != null) {
+ GT_Values.RA.stdBuilder()
+ .itemInputs(GT_OreDictUnificator.get(prefix2, aMaterial, 1L), GT_Utility.getIntegratedCircuit(3))
+ .itemOutputs(GT_OreDictUnificator.get(OrePrefixes.wireFine, aMaterial, 2L * multiplier))
+ .duration(((int) (baseDuration * 0.5f)) * TICKS)
+ .eut(aEUt)
+ .addTo(wiremillRecipes);
+ }
+ }
+
+ public static boolean hasVanillaRecipes(Materials materials) {
+ return Arrays.stream(VANILLA_MATS)
+ .anyMatch(mat -> mat == materials);
+ }
+
+ private static int getRecipeWidth(ShapedOreRecipe r) {
+ try {
+ return (int) SHAPED_ORE_RECIPE_WIDTH.get(r);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getRecipeHeight(ShapedOreRecipe r) {
+ try {
+ return (int) SHAPED_ORE_RECIPE_HEIGHT.get(r);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getRecipeHeight(ShapedRecipes r) {
+ return r.recipeHeight;
+ }
+
+ private static int getRecipeWidth(ShapedRecipes r) {
+ return r.recipeWidth;
+ }
+
+ private static class RecipeShape {
+
+ private final ItemStack[] shape;
+ private int amount1;
+ private int amount2;
+
+ public RecipeShape(ItemStack... shape) {
+ this.shape = shape;
+
+ for (ItemStack stack : shape) {
+ if (stack == sMt1) this.amount1++;
+ if (stack == sMt2) this.amount2++;
+ }
+ }
+
+ public List<List<Integer>> getEmptySlotsAllVariants() {
+ // "shake" the grid in 8 direction and see if the recipe shape is still valid
+ // also include the "no movement" case
+ ImmutableList.Builder<List<Integer>> b = ImmutableList.builder();
+ for (int i = -1; i < 2; i++) {
+ if (i != 0 && !isColClear(i + 1)) continue;
+ for (int j = -1; j < 2; j++) {
+ if (j != 0 && !isRowClear(j + 1)) continue;
+ b.add(getEmptySlots(i, j));
+ }
+ }
+ return b.build();
+ }
+
+ private boolean isRowClear(int row) {
+ for (int i = 0; i < 3; i++) {
+ if (shape[i + row * 3] != null) return false;
+ }
+ return true;
+ }
+
+ private boolean isColClear(int col) {
+ for (int i = 0; i < 3; i++) {
+ if (shape[col + i * 3] != null) return false;
+ }
+ return true;
+ }
+
+ private List<Integer> getEmptySlots(int offsetX, int offsetY) {
+ ImmutableList.Builder<Integer> b = ImmutableList.builder();
+ for (int i = 0; i < shape.length; i++) {
+ int mappedIndex = i - offsetX - offsetY * 3;
+ // empty slot if it either
+ // 1) map to a slot outside the original shape
+ // 2) map to an empty slot in original shape
+ if (mappedIndex < 0 || mappedIndex > 8 || shape[mappedIndex] == null) b.add(i);
+ }
+ return b.build();
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_RenderingWorld.java b/src/main/java/gregtech/api/util/GT_RenderingWorld.java
new file mode 100644
index 0000000000..7220b921a5
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_RenderingWorld.java
@@ -0,0 +1,195 @@
+package gregtech.api.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.world.ChunkCoordIntPair;
+import net.minecraft.world.ChunkPosition;
+import net.minecraft.world.IBlockAccess;
+import net.minecraft.world.biome.BiomeGenBase;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.event.world.ChunkEvent;
+import net.minecraftforge.event.world.WorldEvent;
+
+import cpw.mods.fml.common.FMLCommonHandler;
+import cpw.mods.fml.common.eventhandler.EventPriority;
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import cpw.mods.fml.common.gameevent.TickEvent;
+
+/**
+ * Provide a fake IBlockAccess to support CTM. Facade are supposed to set these when they are placed/received by client.
+ */
+public class GT_RenderingWorld implements IBlockAccess {
+
+ private static final GT_RenderingWorld INSTANCE = new GT_RenderingWorld();
+ /*
+ * I do not think this map would ever grow too huge, so I won't go too overcomplicated on this one
+ */
+ private final Map<ChunkPosition, BlockInfo> infos = new HashMap<>();
+ private final Map<ChunkCoordIntPair, Set<ChunkPosition>> index = new HashMap<>();
+ private IBlockAccess mWorld = Minecraft.getMinecraft().theWorld;
+
+ private GT_RenderingWorld() {
+ new FMLEventHandler();
+ new ForgeEventHandler();
+ }
+
+ public static GT_RenderingWorld getInstance() {
+ return INSTANCE;
+ }
+
+ public static GT_RenderingWorld getInstance(IBlockAccess aWorld) {
+ if (aWorld == INSTANCE) return INSTANCE;
+ if (aWorld == null) INSTANCE.mWorld = Minecraft.getMinecraft().theWorld;
+ else INSTANCE.mWorld = aWorld;
+ return INSTANCE;
+ }
+
+ private void setWorld(IBlockAccess aWorld) {
+ if (aWorld == null) mWorld = Minecraft.getMinecraft().theWorld;
+ else mWorld = aWorld;
+ }
+
+ public void register(int x, int y, int z, Block block, int meta) {
+ ChunkPosition key = new ChunkPosition(x, y, z);
+ infos.put(key, new BlockInfo(block, meta));
+ index.computeIfAbsent(new ChunkCoordIntPair(x >> 4, z >> 4), p -> new HashSet<>())
+ .add(key);
+ }
+
+ public void unregister(int x, int y, int z, Block block, int meta) {
+ ChunkPosition key = new ChunkPosition(x, y, z);
+ if (infos.remove(key, new BlockInfo(block, meta))) {
+ ChunkCoordIntPair chunkKey = new ChunkCoordIntPair(x >> 4, z >> 4);
+ Set<ChunkPosition> set = index.get(chunkKey);
+ set.remove(key);
+ if (set.isEmpty()) index.remove(chunkKey);
+ }
+ }
+
+ @Override
+ public Block getBlock(int p_147439_1_, int p_147439_2_, int p_147439_3_) {
+ BlockInfo blockInfo = infos.get(new ChunkPosition(p_147439_1_, p_147439_2_, p_147439_3_));
+ return blockInfo != null ? blockInfo.block : mWorld.getBlock(p_147439_1_, p_147439_2_, p_147439_3_);
+ }
+
+ @Override
+ public TileEntity getTileEntity(int p_147438_1_, int p_147438_2_, int p_147438_3_) {
+ return mWorld.getTileEntity(p_147438_1_, p_147438_2_, p_147438_3_);
+ }
+
+ @Override
+ public int getLightBrightnessForSkyBlocks(int p_72802_1_, int p_72802_2_, int p_72802_3_, int p_72802_4_) {
+ return mWorld.getLightBrightnessForSkyBlocks(p_72802_1_, p_72802_2_, p_72802_3_, p_72802_4_);
+ }
+
+ @Override
+ public int getBlockMetadata(int p_72805_1_, int p_72805_2_, int p_72805_3_) {
+ BlockInfo blockInfo = infos.get(new ChunkPosition(p_72805_1_, p_72805_2_, p_72805_3_));
+ return blockInfo != null ? blockInfo.meta : mWorld.getBlockMetadata(p_72805_1_, p_72805_2_, p_72805_3_);
+ }
+
+ @Override
+ public int isBlockProvidingPowerTo(int p_72879_1_, int p_72879_2_, int p_72879_3_, int p_72879_4_) {
+ return mWorld.isBlockProvidingPowerTo(p_72879_1_, p_72879_2_, p_72879_3_, p_72879_4_);
+ }
+
+ @Override
+ public boolean isAirBlock(int p_147437_1_, int p_147437_2_, int p_147437_3_) {
+ return getBlock(p_147437_1_, p_147437_2_, p_147437_3_).isAir(mWorld, p_147437_1_, p_147437_2_, p_147437_3_);
+ }
+
+ @Override
+ public BiomeGenBase getBiomeGenForCoords(int p_72807_1_, int p_72807_2_) {
+ return mWorld.getBiomeGenForCoords(p_72807_1_, p_72807_2_);
+ }
+
+ @Override
+ public int getHeight() {
+ return mWorld.getHeight();
+ }
+
+ @Override
+ public boolean extendedLevelsInChunkCache() {
+ return mWorld.extendedLevelsInChunkCache();
+ }
+
+ @Override
+ public boolean isSideSolid(int x, int y, int z, ForgeDirection side, boolean _default) {
+ return getBlock(x, y, z).isSideSolid(this, x, y, z, side);
+ }
+
+ public class FMLEventHandler {
+
+ public FMLEventHandler() {
+ FMLCommonHandler.instance()
+ .bus()
+ .register(this);
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onRenderTickStart(TickEvent.RenderTickEvent e) {
+ if (e.phase == TickEvent.Phase.START) mWorld = Minecraft.getMinecraft().theWorld;
+ }
+ }
+
+ public class ForgeEventHandler {
+
+ private ForgeEventHandler() {
+ MinecraftForge.EVENT_BUS.register(this);
+ }
+
+ @SubscribeEvent
+ public void onChunkUnloaded(ChunkEvent.Unload e) {
+ if (!e.world.isRemote) return;
+ Set<ChunkPosition> set = index.remove(
+ e.getChunk()
+ .getChunkCoordIntPair());
+ if (set != null) infos.keySet()
+ .removeAll(set);
+ }
+
+ @SubscribeEvent
+ public void onWorldUnloaded(WorldEvent.Unload e) {
+ if (!e.world.isRemote) return;
+ infos.clear();
+ index.clear();
+ }
+ }
+
+ private static class BlockInfo {
+
+ private final Block block;
+ private final int meta;
+
+ public BlockInfo(Block block, int meta) {
+ this.block = block;
+ this.meta = meta;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ BlockInfo blockInfo = (BlockInfo) o;
+
+ if (meta != blockInfo.meta) return false;
+ return Objects.equals(block, blockInfo.block);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = block != null ? block.hashCode() : 0;
+ result = 31 * result + meta;
+ return result;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Shaped_Recipe.java b/src/main/java/gregtech/api/util/GT_Shaped_Recipe.java
new file mode 100644
index 0000000000..95a1a0bb66
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Shaped_Recipe.java
@@ -0,0 +1,100 @@
+package gregtech.api.util;
+
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.enchantment.EnchantmentHelper;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.oredict.ShapedOreRecipe;
+
+import gregtech.api.interfaces.internal.IGT_CraftingRecipe;
+
+public class GT_Shaped_Recipe extends ShapedOreRecipe implements IGT_CraftingRecipe {
+
+ public final boolean mRemovableByGT, mKeepingNBT;
+ private final Enchantment[] mEnchantmentsAdded;
+ private final int[] mEnchantmentLevelsAdded;
+
+ public GT_Shaped_Recipe(ItemStack aResult, boolean aDismantleAble, boolean aRemovableByGT, boolean aKeepingNBT,
+ Enchantment[] aEnchantmentsAdded, int[] aEnchantmentLevelsAdded, Object... aRecipe) {
+ super(aResult, aRecipe);
+ mEnchantmentsAdded = aEnchantmentsAdded;
+ mEnchantmentLevelsAdded = aEnchantmentLevelsAdded;
+ mRemovableByGT = aRemovableByGT;
+ mKeepingNBT = aKeepingNBT;
+ }
+
+ @Override
+ public boolean matches(InventoryCrafting aGrid, World aWorld) {
+ if (mKeepingNBT) {
+ ItemStack tStack = null;
+ for (int i = 0; i < aGrid.getSizeInventory(); i++) {
+ if (aGrid.getStackInSlot(i) != null) {
+ if (tStack != null) {
+ if ((tStack.hasTagCompound() != aGrid.getStackInSlot(i)
+ .hasTagCompound()) || (tStack.hasTagCompound()
+ && !tStack.getTagCompound()
+ .equals(
+ aGrid.getStackInSlot(i)
+ .getTagCompound())))
+ return false;
+ }
+ tStack = aGrid.getStackInSlot(i);
+ }
+ }
+ }
+ return super.matches(aGrid, aWorld);
+ }
+
+ @Override
+ public ItemStack getCraftingResult(InventoryCrafting aGrid) {
+ ItemStack rStack = super.getCraftingResult(aGrid);
+ if (rStack != null) {
+ // Update the Stack
+ GT_Utility.updateItemStack(rStack);
+
+ // Keeping NBT
+ if (mKeepingNBT) for (int i = 0; i < aGrid.getSizeInventory(); i++) {
+ if (aGrid.getStackInSlot(i) != null && aGrid.getStackInSlot(i)
+ .hasTagCompound()) {
+ rStack.setTagCompound(
+ (NBTTagCompound) aGrid.getStackInSlot(i)
+ .getTagCompound()
+ .copy());
+ break;
+ }
+ }
+
+ // Charge Values
+ if (GT_ModHandler.isElectricItem(rStack)) {
+ GT_ModHandler.dischargeElectricItem(rStack, Integer.MAX_VALUE, Integer.MAX_VALUE, true, false, true);
+ int tCharge = 0;
+ for (int i = 0; i < aGrid.getSizeInventory(); i++) tCharge += GT_ModHandler.dischargeElectricItem(
+ aGrid.getStackInSlot(i),
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ true,
+ true,
+ true);
+ if (tCharge > 0) GT_ModHandler.chargeElectricItem(rStack, tCharge, Integer.MAX_VALUE, true, false);
+ }
+
+ // Add Enchantments
+ for (int i = 0; i < mEnchantmentsAdded.length; i++) GT_Utility.ItemNBT.addEnchantment(
+ rStack,
+ mEnchantmentsAdded[i],
+ EnchantmentHelper.getEnchantmentLevel(mEnchantmentsAdded[i].effectId, rStack)
+ + mEnchantmentLevelsAdded[i]);
+
+ // Update the Stack again
+ GT_Utility.updateItemStack(rStack);
+ }
+ return rStack;
+ }
+
+ @Override
+ public boolean isRemovable() {
+ return mRemovableByGT;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Shapeless_Recipe.java b/src/main/java/gregtech/api/util/GT_Shapeless_Recipe.java
new file mode 100644
index 0000000000..582dd7cc10
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Shapeless_Recipe.java
@@ -0,0 +1,100 @@
+package gregtech.api.util;
+
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.enchantment.EnchantmentHelper;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.oredict.ShapelessOreRecipe;
+
+import gregtech.api.interfaces.internal.IGT_CraftingRecipe;
+
+public class GT_Shapeless_Recipe extends ShapelessOreRecipe implements IGT_CraftingRecipe {
+
+ public final boolean mRemovableByGT, mKeepingNBT;
+ private final Enchantment[] mEnchantmentsAdded;
+ private final int[] mEnchantmentLevelsAdded;
+
+ public GT_Shapeless_Recipe(ItemStack aResult, boolean aDismantleAble, boolean aRemovableByGT, boolean aKeepingNBT,
+ Enchantment[] aEnchantmentsAdded, int[] aEnchantmentLevelsAdded, Object... aRecipe) {
+ super(aResult, aRecipe);
+ mEnchantmentsAdded = aEnchantmentsAdded;
+ mEnchantmentLevelsAdded = aEnchantmentLevelsAdded;
+ mRemovableByGT = aRemovableByGT;
+ mKeepingNBT = aKeepingNBT;
+ }
+
+ @Override
+ public boolean matches(InventoryCrafting aGrid, World aWorld) {
+ if (mKeepingNBT) {
+ ItemStack tStack = null;
+ for (int i = 0; i < aGrid.getSizeInventory(); i++) {
+ if (aGrid.getStackInSlot(i) != null) {
+ if (tStack != null) {
+ if ((tStack.hasTagCompound() != aGrid.getStackInSlot(i)
+ .hasTagCompound()) || (tStack.hasTagCompound()
+ && !tStack.getTagCompound()
+ .equals(
+ aGrid.getStackInSlot(i)
+ .getTagCompound())))
+ return false;
+ }
+ tStack = aGrid.getStackInSlot(i);
+ }
+ }
+ }
+ return super.matches(aGrid, aWorld);
+ }
+
+ @Override
+ public ItemStack getCraftingResult(InventoryCrafting aGrid) {
+ ItemStack rStack = super.getCraftingResult(aGrid);
+ if (rStack != null) {
+ // Update the Stack
+ GT_Utility.updateItemStack(rStack);
+
+ // Keeping NBT
+ if (mKeepingNBT) for (int i = 0; i < aGrid.getSizeInventory(); i++) {
+ if (aGrid.getStackInSlot(i) != null && aGrid.getStackInSlot(i)
+ .hasTagCompound()) {
+ rStack.setTagCompound(
+ (NBTTagCompound) aGrid.getStackInSlot(i)
+ .getTagCompound()
+ .copy());
+ break;
+ }
+ }
+
+ // Charge Values
+ if (GT_ModHandler.isElectricItem(rStack)) {
+ GT_ModHandler.dischargeElectricItem(rStack, Integer.MAX_VALUE, Integer.MAX_VALUE, true, false, true);
+ int tCharge = 0;
+ for (int i = 0; i < aGrid.getSizeInventory(); i++) tCharge += GT_ModHandler.dischargeElectricItem(
+ aGrid.getStackInSlot(i),
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ true,
+ true,
+ true);
+ if (tCharge > 0) GT_ModHandler.chargeElectricItem(rStack, tCharge, Integer.MAX_VALUE, true, false);
+ }
+
+ // Add Enchantments
+ for (int i = 0; i < mEnchantmentsAdded.length; i++) GT_Utility.ItemNBT.addEnchantment(
+ rStack,
+ mEnchantmentsAdded[i],
+ EnchantmentHelper.getEnchantmentLevel(mEnchantmentsAdded[i].effectId, rStack)
+ + mEnchantmentLevelsAdded[i]);
+
+ // Update the Stack again
+ GT_Utility.updateItemStack(rStack);
+ }
+ return rStack;
+ }
+
+ @Override
+ public boolean isRemovable() {
+ return mRemovableByGT;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_SpawnEventHandler.java b/src/main/java/gregtech/api/util/GT_SpawnEventHandler.java
new file mode 100644
index 0000000000..ebdba1144b
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_SpawnEventHandler.java
@@ -0,0 +1,81 @@
+package gregtech.api.util;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import net.minecraft.entity.EnumCreatureType;
+import net.minecraft.entity.monster.EntitySlime;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.living.LivingSpawnEvent.CheckSpawn;
+
+import cpw.mods.fml.common.eventhandler.Event;
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.metatileentity.BaseMetaTileEntity;
+import gregtech.common.tileentities.machines.basic.GT_MetaTileEntity_MonsterRepellent;
+
+public class GT_SpawnEventHandler {
+
+ public static volatile List<int[]> mobReps = new CopyOnWriteArrayList<>();
+ // Future Optimiztation ideas, if this isn't sufficient
+ // 1: Keep a weakref list of mob repellents so we already have the tile
+ // 2: Have the tick method update a HashMap of (int[], range) so we don't have to load the tile at all
+
+ public GT_SpawnEventHandler() {
+ MinecraftForge.EVENT_BUS.register(this);
+ }
+
+ // Range of a powered repellent
+ public static int getPoweredRepellentRange(int aTier) {
+ return 16 + (48 * aTier);
+ }
+
+ // Range of an unpowered repellent
+ public static int getUnpoweredRepellentRange(int aTier) {
+ return 4 + (12 * aTier);
+ }
+
+ @SubscribeEvent
+ public void denyMobSpawn(CheckSpawn event) {
+ if (event.getResult() == Event.Result.DENY) return;
+
+ if (event.entityLiving instanceof EntitySlime slime && !slime.hasCustomNameTag()
+ && event.getResult() == Event.Result.ALLOW) {
+ event.setResult(Event.Result.DEFAULT);
+ }
+
+ if (event.getResult() == Event.Result.ALLOW) {
+ return;
+ }
+
+ if (event.entityLiving.isCreatureType(EnumCreatureType.monster, false)) {
+ final double maxRangeCheck = Math.pow(getPoweredRepellentRange(GT_Values.V.length - 1), 2);
+ for (int[] rep : mobReps) {
+ if (rep[3] == event.entity.worldObj.provider.dimensionId) {
+ // If the chunk isn't loaded, we ignore this Repellent
+ if (!event.entity.worldObj.blockExists(rep[0], rep[1], rep[2])) continue;
+ final double dx = rep[0] + 0.5F - event.entity.posX;
+ final double dy = rep[1] + 0.5F - event.entity.posY;
+ final double dz = rep[2] + 0.5F - event.entity.posZ;
+
+ final double check = (dx * dx + dz * dz + dy * dy);
+ // Fail early if outside of max range
+ if (check > maxRangeCheck) continue;
+
+ final TileEntity tTile = event.entity.worldObj.getTileEntity(rep[0], rep[1], rep[2]);
+ if (tTile instanceof BaseMetaTileEntity metaTile
+ && metaTile.getMetaTileEntity() instanceof GT_MetaTileEntity_MonsterRepellent repellent
+ && check <= Math.pow(repellent.mRange, 2)) {
+ if (event.entityLiving instanceof EntitySlime slime) {
+ slime.setCustomNameTag("DoNotSpawnSlimes");
+ }
+ event.setResult(Event.Result.DENY);
+ // We're already DENYing it. No reason to keep checking
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_StreamUtil.java b/src/main/java/gregtech/api/util/GT_StreamUtil.java
new file mode 100644
index 0000000000..c29e611c4e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_StreamUtil.java
@@ -0,0 +1,44 @@
+package gregtech.api.util;
+
+import java.util.Arrays;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public final class GT_StreamUtil {
+
+ /**
+ * Backport of {@link Stream#ofNullable}.
+ */
+ public static <T> Stream<T> ofNullable(@Nullable T value) {
+ return value == null ? Stream.empty() : Stream.of(value);
+ }
+
+ /**
+ * Returns a sequential ordered {@code Stream} whose elements are the specified values,
+ * if {@code condition} is true, otherwise returns an empty {@code Stream}.
+ *
+ * @param <T> the type of stream elements
+ * @param values the elements of the new stream
+ * @return the new stream
+ */
+ public static <T> Stream<T> ofConditional(boolean condition, T[] values) {
+ return condition ? Arrays.stream(values) : Stream.empty();
+ }
+
+ /**
+ * Returns a sequential {@code Stream} containing a single element, which will be lazily evaluated from supplier.
+ *
+ * @param <T> the type of stream elements
+ * @param supplier the supplier for single stream element
+ * @return the new stream
+ */
+ public static <T> Stream<T> ofSupplier(Supplier<T> supplier) {
+ return Stream.generate(supplier)
+ .limit(1);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_StructureUtility.java b/src/main/java/gregtech/api/util/GT_StructureUtility.java
new file mode 100644
index 0000000000..d3c4c10a0d
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_StructureUtility.java
@@ -0,0 +1,512 @@
+package gregtech.api.util;
+
+import static com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult.ACCEPT;
+import static com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult.ACCEPT_STOP;
+import static com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult.REJECT;
+import static com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult.SKIP;
+import static com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode.EXACT;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChatComponentTranslation;
+import net.minecraft.util.IChatComponent;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
+import com.gtnewhorizon.structurelib.structure.IItemSource;
+import com.gtnewhorizon.structurelib.structure.IStructureElement;
+import com.gtnewhorizon.structurelib.structure.IStructureElementNoPlacement;
+import com.gtnewhorizon.structurelib.structure.StructureUtility;
+import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.HeatingCoilLevel;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.interfaces.IHeatingCoil;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.metatileentity.BaseMetaPipeEntity;
+import gregtech.api.metatileentity.implementations.GT_MetaPipeEntity_Frame;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_TieredMachineBlock;
+import gregtech.common.blocks.GT_Block_Casings5;
+import gregtech.common.blocks.GT_Item_Machines;
+
+public class GT_StructureUtility {
+
+ // private static final Map<Class<?>, String> customNames = new HashMap<>();
+ private GT_StructureUtility() {
+ throw new AssertionError("Not instantiable");
+ }
+
+ public static boolean hasMTE(IGregTechTileEntity aTile, Class<? extends IMetaTileEntity> clazz) {
+ return aTile != null && clazz.isInstance(aTile.getMetaTileEntity());
+ }
+
+ public static <T> IStructureElementNoPlacement<T> ofHatchAdder(IGT_HatchAdder<T> aHatchAdder, int aTextureIndex,
+ int aDots) {
+ return ofHatchAdder(aHatchAdder, aTextureIndex, StructureLibAPI.getBlockHint(), aDots - 1);
+ }
+
+ public static <T> IStructureElement<T> ofFrame(Materials aFrameMaterial) {
+ if (aFrameMaterial == null) throw new IllegalArgumentException();
+ return new IStructureElement<>() {
+
+ private IIcon[] mIcons;
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tBase = world.getTileEntity(x, y, z);
+ if (tBase instanceof BaseMetaPipeEntity tPipeBase) {
+ if (tPipeBase.isInvalidTileEntity()) return false;
+ if (tPipeBase.getMetaTileEntity() instanceof GT_MetaPipeEntity_Frame)
+ return aFrameMaterial == ((GT_MetaPipeEntity_Frame) tPipeBase.getMetaTileEntity()).mMaterial;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ if (mIcons == null) {
+ mIcons = new IIcon[6];
+ Arrays.fill(mIcons, aFrameMaterial.mIconSet.mTextures[OrePrefixes.frameGt.mTextureIndex].getIcon());
+ }
+ StructureLibAPI.hintParticleTinted(world, x, y, z, mIcons, aFrameMaterial.mRGBa);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
+ ItemStack tFrameStack = getFrameStack();
+ if (!GT_Utility.isStackValid(tFrameStack)
+ || !(tFrameStack.getItem() instanceof ItemBlock tFrameStackItem)) return false;
+ return tFrameStackItem
+ .placeBlockAt(tFrameStack, null, world, x, y, z, 6, 0, 0, 0, Items.feather.getDamage(tFrameStack));
+ }
+
+ private ItemStack getFrameStack() {
+ return GT_OreDictUnificator.get(OrePrefixes.frameGt, aFrameMaterial, 1);
+ }
+
+ @Override
+ public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ ItemStack tFrameStack = getFrameStack();
+ if (!GT_Utility.isStackValid(tFrameStack) || !(tFrameStack.getItem() instanceof ItemBlock))
+ return BlocksToPlace.errored;
+ return BlocksToPlace.create(tFrameStack);
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ return survivalPlaceBlock(
+ t,
+ world,
+ x,
+ y,
+ z,
+ trigger,
+ AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ if (check(t, world, x, y, z)) return SKIP;
+ ItemStack tFrameStack = getFrameStack();
+ if (!GT_Utility.isStackValid(tFrameStack) || !(tFrameStack.getItem() instanceof ItemBlock))
+ return REJECT; // honestly, this is more like a programming error or pack issue
+ return StructureUtility.survivalPlaceBlock(
+ tFrameStack,
+ ItemStackPredicate.NBTMode.IGNORE_KNOWN_INSIGNIFICANT_TAGS,
+ null,
+ false,
+ world,
+ x,
+ y,
+ z,
+ env.getSource(),
+ env.getActor(),
+ env.getChatter());
+ }
+ };
+ }
+
+ public static <T> GT_HatchElementBuilder<T> buildHatchAdder() {
+ return GT_HatchElementBuilder.builder();
+ }
+
+ /**
+ * Completely equivalent to {@link #buildHatchAdder()}, except it plays nicer with type inference when statically
+ * imported
+ */
+ public static <T> GT_HatchElementBuilder<T> buildHatchAdder(Class<T> typeToken) {
+ return GT_HatchElementBuilder.builder();
+ }
+
+ public static <T> IStructureElementNoPlacement<T> ofHatchAdder(IGT_HatchAdder<T> aHatchAdder, int aTextureIndex,
+ Block aHintBlock, int aHintMeta) {
+ if (aHatchAdder == null || aHintBlock == null) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElementNoPlacement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ return tileEntity instanceof IGregTechTileEntity
+ && aHatchAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) aTextureIndex);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, aHintBlock, aHintMeta);
+ return true;
+ }
+ };
+ }
+
+ public static <T> IStructureElement<T> ofHatchAdder(IGT_HatchAdder<T> aHatchAdder, int aTextureIndex,
+ Block aHintBlock, int aHintMeta, BiPredicate<T, IGregTechTileEntity> shouldSkip,
+ Function<T, Class<? extends IMetaTileEntity>> aMetaId, final IStructureElement.PlaceResult acceptType) {
+ if (aHatchAdder == null) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ return tileEntity instanceof IGregTechTileEntity
+ && aHatchAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) aTextureIndex);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, aHintBlock, aHintMeta);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int i, int i1, int i2, ItemStack itemStack) {
+ // TODO
+ return false;
+ }
+
+ @Override
+ public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ Class<? extends IMetaTileEntity> clazz = aMetaId.apply(t);
+ if (clazz == null) return BlocksToPlace.createEmpty();
+ return BlocksToPlace.create(is -> clazz.isInstance(GT_Item_Machines.getMetaTileEntity(is)));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ return survivalPlaceBlock(
+ t,
+ world,
+ x,
+ y,
+ z,
+ trigger,
+ AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ if (shouldSkip != null) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (tileEntity instanceof IGregTechTileEntity
+ && shouldSkip.test(t, (IGregTechTileEntity) tileEntity)) return SKIP;
+ }
+ if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, env.getActor())) return REJECT;
+ Class<? extends IMetaTileEntity> clazz = aMetaId.apply(t);
+ if (clazz == null) return REJECT;
+ ItemStack taken = env.getSource()
+ .takeOne(is -> clazz.isInstance(GT_Item_Machines.getMetaTileEntity(is)), true);
+ if (GT_Utility.isStackInvalid(taken)) {
+ env.getChatter()
+ .accept(
+ new ChatComponentTranslation(
+ "GT5U.autoplace.error.no_mte.class_name",
+ clazz.getSimpleName()));
+ return REJECT;
+ }
+ if (StructureUtility
+ .survivalPlaceBlock(taken, EXACT, null, true, world, x, y, z, env.getSource(), env.getActor())
+ == ACCEPT) return acceptType;
+ return REJECT;
+ }
+ };
+ }
+
+ public static <T> IStructureElement<T> ofHatchAdder(IGT_HatchAdder<T> aHatchAdder, int aTextureIndex,
+ Block aHintBlock, int aHintMeta, BiPredicate<T, IGregTechTileEntity> shouldSkip, ToIntFunction<T> aMetaId) {
+ if (aHatchAdder == null) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ return tileEntity instanceof IGregTechTileEntity
+ && aHatchAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) aTextureIndex);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, aHintBlock, aHintMeta);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int i, int i1, int i2, ItemStack itemStack) {
+ // TODO
+ return false;
+ }
+
+ @Override
+ public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ GT_Item_Machines item = (GT_Item_Machines) Item.getItemFromBlock(GregTech_API.sBlockMachines);
+ int meta = aMetaId.applyAsInt(t);
+ if (meta < 0) return BlocksToPlace.createEmpty();
+ return BlocksToPlace.create(
+ ItemStackPredicate.from(item)
+ .setMeta(meta));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ return survivalPlaceBlock(
+ t,
+ world,
+ x,
+ y,
+ z,
+ trigger,
+ AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ if (shouldSkip != null) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (tileEntity instanceof IGregTechTileEntity
+ && shouldSkip.test(t, (IGregTechTileEntity) tileEntity)) return SKIP;
+ }
+ if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, env.getActor())) return REJECT;
+ GT_Item_Machines item = (GT_Item_Machines) Item.getItemFromBlock(GregTech_API.sBlockMachines);
+ int meta = aMetaId.applyAsInt(t);
+ if (meta < 0) return REJECT;
+ ItemStack taken = env.getSource()
+ .takeOne(
+ ItemStackPredicate.from(item)
+ .setMeta(meta),
+ true);
+ if (GT_Utility.isStackInvalid(taken)) {
+ env.getChatter()
+ .accept(new ChatComponentTranslation("GT5U.autoplace.error.no_mte.id", meta));
+ return REJECT;
+ }
+ return StructureUtility
+ .survivalPlaceBlock(taken, EXACT, null, true, world, x, y, z, env.getSource(), env.getActor())
+ == ACCEPT ? ACCEPT_STOP : REJECT;
+ }
+ };
+ }
+
+ public static <T> IStructureElement<T> ofHatchAdderOptional(IGT_HatchAdder<T> aHatchAdder, int textureIndex,
+ int dots, Block placeCasing, int placeCasingMeta) {
+ return ofHatchAdderOptional(
+ aHatchAdder,
+ textureIndex,
+ StructureLibAPI.getBlockHint(),
+ dots - 1,
+ placeCasing,
+ placeCasingMeta);
+ }
+
+ public static <T> IStructureElement<T> ofHatchAdderOptional(IGT_HatchAdder<T> aHatchAdder, int aTextureIndex,
+ Block aHintBlock, int hintMeta, Block placeCasing, int placeCasingMeta) {
+ if (aHatchAdder == null || aHintBlock == null) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ TileEntity tileEntity = world.getTileEntity(x, y, z);
+ Block worldBlock = world.getBlock(x, y, z);
+ return (tileEntity instanceof IGregTechTileEntity
+ && aHatchAdder.apply(t, (IGregTechTileEntity) tileEntity, (short) aTextureIndex))
+ || (worldBlock == placeCasing && worldBlock.getDamageValue(world, x, y, z) == placeCasingMeta);
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, aHintBlock, hintMeta);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
+ world.setBlock(x, y, z, placeCasing, placeCasingMeta, 2);
+ return true;
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ if (check(t, world, x, y, z)) return SKIP;
+ return StructureUtility
+ .survivalPlaceBlock(placeCasing, placeCasingMeta, world, x, y, z, s, actor, chatter);
+ }
+ };
+ }
+
+ /**
+ * Assume all coils accepted.
+ *
+ * @see #ofCoil(BiPredicate, Function)
+ */
+ public static <T> IStructureElement<T> ofCoil(BiConsumer<T, HeatingCoilLevel> aHeatingCoilSetter,
+ Function<T, HeatingCoilLevel> aHeatingCoilGetter) {
+ return ofCoil((t, l) -> {
+ aHeatingCoilSetter.accept(t, l);
+ return true;
+ }, aHeatingCoilGetter);
+ }
+
+ /**
+ * Heating coil structure element.
+ *
+ * @param aHeatingCoilSetter Notify the controller of this new coil. Got called exactly once per coil. Might be
+ * called less times if structure test fails. If the setter returns false then it assumes
+ * the coil is rejected.
+ * @param aHeatingCoilGetter Get the current heating level. Null means no coil recorded yet.
+ */
+ public static <T> IStructureElement<T> ofCoil(BiPredicate<T, HeatingCoilLevel> aHeatingCoilSetter,
+ Function<T, HeatingCoilLevel> aHeatingCoilGetter) {
+ if (aHeatingCoilSetter == null || aHeatingCoilGetter == null) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElement<>() {
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ Block block = world.getBlock(x, y, z);
+ if (!(block instanceof IHeatingCoil)) return false;
+ HeatingCoilLevel existingLevel = aHeatingCoilGetter.apply(t),
+ newLevel = ((IHeatingCoil) block).getCoilHeat(world.getBlockMetadata(x, y, z));
+ if (existingLevel == null || existingLevel == HeatingCoilLevel.None) {
+ return aHeatingCoilSetter.test(t, newLevel);
+ } else {
+ return newLevel == existingLevel;
+ }
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ StructureLibAPI.hintParticle(world, x, y, z, GregTech_API.sBlockCasings5, getMetaFromHint(trigger));
+ return true;
+ }
+
+ private int getMetaFromHint(ItemStack trigger) {
+ return GT_Block_Casings5.getMetaFromCoilHeat(getHeatFromHint(trigger));
+ }
+
+ private HeatingCoilLevel getHeatFromHint(ItemStack trigger) {
+ return HeatingCoilLevel
+ .getFromTier((byte) Math.min(HeatingCoilLevel.getMaxTier(), Math.max(0, trigger.stackSize - 1)));
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
+ return world.setBlock(x, y, z, GregTech_API.sBlockCasings5, getMetaFromHint(trigger), 3);
+ }
+
+ @Override
+ public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ return BlocksToPlace.create(GregTech_API.sBlockCasings5, getMetaFromHint(trigger));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
+ return survivalPlaceBlock(
+ t,
+ world,
+ x,
+ y,
+ z,
+ trigger,
+ AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
+ }
+
+ @Override
+ public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
+ AutoPlaceEnvironment env) {
+ Block block = world.getBlock(x, y, z);
+ boolean isCoil = block instanceof IHeatingCoil
+ && ((IHeatingCoil) block).getCoilHeat(world.getBlockMetadata(x, y, z)) == getHeatFromHint(trigger);
+ if (isCoil) return SKIP;
+ return StructureUtility.survivalPlaceBlock(
+ GregTech_API.sBlockCasings5,
+ getMetaFromHint(trigger),
+ world,
+ x,
+ y,
+ z,
+ env.getSource(),
+ env.getActor(),
+ env.getChatter());
+ }
+ };
+ }
+
+ @Nonnull
+ public static Predicate<ItemStack> filterByMTEClass(List<? extends Class<? extends IMetaTileEntity>> list) {
+ return is -> {
+ IMetaTileEntity tile = GT_Item_Machines.getMetaTileEntity(is);
+ return tile != null && list.stream()
+ .anyMatch(c -> c.isInstance(tile));
+ };
+ }
+
+ @Nonnull
+ public static Predicate<ItemStack> filterByMTETier(int aMinTier, int aMaxTier) {
+ return is -> {
+ IMetaTileEntity tile = GT_Item_Machines.getMetaTileEntity(is);
+ return tile instanceof GT_MetaTileEntity_TieredMachineBlock
+ && ((GT_MetaTileEntity_TieredMachineBlock) tile).mTier <= aMaxTier
+ && ((GT_MetaTileEntity_TieredMachineBlock) tile).mTier >= aMinTier;
+ };
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_StructureUtilityMuTE.java b/src/main/java/gregtech/api/util/GT_StructureUtilityMuTE.java
new file mode 100644
index 0000000000..8e8d027463
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_StructureUtilityMuTE.java
@@ -0,0 +1,271 @@
+package gregtech.api.util;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.multitileentity.enums.GT_MultiTileComponentCasing.*;
+import static gregtech.api.multitileentity.enums.GT_MultiTileUpgradeCasing.*;
+import static gregtech.loaders.preload.GT_Loader_MultiTileEntities.*;
+
+import java.util.Arrays;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.structure.IStructureElement;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.TextureSet;
+import gregtech.api.multitileentity.MultiTileEntityContainer;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
+import gregtech.api.multitileentity.enums.GT_MultiTileUpgradeCasing;
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.multiblock.base.Controller;
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public class GT_StructureUtilityMuTE {
+
+ public static final MuTEStructureCasing MOTOR_CASINGS = FunctionalCasings.Motor.getCasing();
+ public static final MuTEStructureCasing PUMP_CASINGS = FunctionalCasings.Pump.getCasing();
+ public static final MuTEStructureCasing CONVEYOR_CASINGS = FunctionalCasings.Conveyor.getCasing();
+ public static final MuTEStructureCasing PISTON_CASINGS = FunctionalCasings.Piston.getCasing();
+ public static final MuTEStructureCasing ROBOT_ARM_CASINGS = FunctionalCasings.RobotArm.getCasing();
+ public static final MuTEStructureCasing EMITTER_CASINGS = FunctionalCasings.Emitter.getCasing();
+ public static final MuTEStructureCasing SENSOR_CASINGS = FunctionalCasings.Sensor.getCasing();
+ public static final MuTEStructureCasing FIELD_GENERATOR_CASINGS = FunctionalCasings.FieldGenerator.getCasing();
+ public static final MuTEStructureCasing INVENTORY_CASINGS = UpgradeCasings.Inventory.getCasing();
+ public static final MuTEStructureCasing TANK_CASINGS = UpgradeCasings.Tank.getCasing();
+ public static final MuTEStructureCasing AMPERAGE_CASINGS = UpgradeCasings.Amperage.getCasing();
+ public static final MuTEStructureCasing LASER_CASINGS = UpgradeCasings.Laser.getCasing();
+ public static final MuTEStructureCasing WIRELESS_CASINGS = UpgradeCasings.Wireless.getCasing();
+ public static final MuTEStructureCasing CLEANROOM_CASINGS = UpgradeCasings.Cleanroom.getCasing();
+ public static final MuTEStructureCasing HEATER_CASINGS = UpgradeCasings.Heater.getCasing();
+ public static final MuTEStructureCasing INSULATOR_CASINGS = UpgradeCasings.Insulator.getCasing();
+
+ public enum FunctionalCasings {
+
+ Motor(COMPONENT_CASING_REGISTRY_NAME, LV_Motor.getId(), MV_Motor.getId(), HV_Motor.getId(), EV_Motor.getId(),
+ IV_Motor.getId(), LuV_Motor.getId(), ZPM_Motor.getId(), UV_Motor.getId(), UHV_Motor.getId(),
+ UEV_Motor.getId(), UIV_Motor.getId(), UMV_Motor.getId(), UXV_Motor.getId(), MAX_Motor.getId()),
+
+ Pump(COMPONENT_CASING_REGISTRY_NAME, LV_Pump.getId(), MV_Pump.getId(), HV_Pump.getId(), EV_Pump.getId(),
+ IV_Pump.getId(), LuV_Pump.getId(), ZPM_Pump.getId(), UV_Pump.getId(), UHV_Pump.getId(), UEV_Pump.getId(),
+ UIV_Pump.getId(), UMV_Pump.getId(), UXV_Pump.getId(), MAX_Pump.getId()),
+
+ Conveyor(COMPONENT_CASING_REGISTRY_NAME, LV_Conveyor.getId(), MV_Conveyor.getId(), HV_Conveyor.getId(),
+ EV_Conveyor.getId(), IV_Conveyor.getId(), LuV_Conveyor.getId(), ZPM_Conveyor.getId(), UV_Conveyor.getId(),
+ UHV_Conveyor.getId(), UEV_Conveyor.getId(), UIV_Conveyor.getId(), UMV_Conveyor.getId(),
+ UXV_Conveyor.getId(), MAX_Conveyor.getId()),
+
+ Piston(COMPONENT_CASING_REGISTRY_NAME, LV_Piston.getId(), MV_Piston.getId(), HV_Piston.getId(),
+ EV_Piston.getId(), IV_Piston.getId(), LuV_Piston.getId(), ZPM_Piston.getId(), UV_Piston.getId(),
+ UHV_Piston.getId(), UEV_Piston.getId(), UIV_Piston.getId(), UMV_Piston.getId(), UXV_Piston.getId(),
+ MAX_Piston.getId()),
+
+ RobotArm(COMPONENT_CASING_REGISTRY_NAME, LV_RobotArm.getId(), MV_RobotArm.getId(), HV_RobotArm.getId(),
+ EV_RobotArm.getId(), IV_RobotArm.getId(), LuV_RobotArm.getId(), ZPM_RobotArm.getId(), UV_RobotArm.getId(),
+ UHV_RobotArm.getId(), UEV_RobotArm.getId(), UIV_RobotArm.getId(), UMV_RobotArm.getId(),
+ UXV_RobotArm.getId(), MAX_RobotArm.getId()),
+
+ Emitter(COMPONENT_CASING_REGISTRY_NAME, LV_Emitter.getId(), MV_Emitter.getId(), HV_Emitter.getId(),
+ EV_Emitter.getId(), IV_Emitter.getId(), LuV_Emitter.getId(), ZPM_Emitter.getId(), UV_Emitter.getId(),
+ UHV_Emitter.getId(), UEV_Emitter.getId(), UIV_Emitter.getId(), UMV_Emitter.getId(), UXV_Emitter.getId(),
+ MAX_Emitter.getId()),
+
+ Sensor(COMPONENT_CASING_REGISTRY_NAME, LV_Sensor.getId(), MV_Sensor.getId(), HV_Sensor.getId(),
+ EV_Sensor.getId(), IV_Sensor.getId(), LuV_Sensor.getId(), ZPM_Sensor.getId(), UV_Sensor.getId(),
+ UHV_Sensor.getId(), UEV_Sensor.getId(), UIV_Sensor.getId(), UMV_Sensor.getId(), UXV_Sensor.getId(),
+ MAX_Sensor.getId()),
+
+ FieldGenerator(COMPONENT_CASING_REGISTRY_NAME, LV_FieldGenerator.getId(), MV_FieldGenerator.getId(),
+ HV_FieldGenerator.getId(), EV_FieldGenerator.getId(), IV_FieldGenerator.getId(), LuV_FieldGenerator.getId(),
+ ZPM_FieldGenerator.getId(), UV_FieldGenerator.getId(), UHV_FieldGenerator.getId(),
+ UEV_FieldGenerator.getId(), UIV_FieldGenerator.getId(), UMV_FieldGenerator.getId(),
+ UXV_FieldGenerator.getId(), MAX_FieldGenerator.getId());
+
+ private final MuTEStructureCasing casing;
+
+ FunctionalCasings(String registryName, Integer... validIds) {
+ casing = createMuTEStructureCasing(registryName, validIds);
+ }
+
+ public MuTEStructureCasing getCasing() {
+ return casing;
+ }
+ }
+
+ public enum UpgradeCasings {
+
+ Inventory(UPGRADE_CASING_REGISTRY_NAME, ULV_Inventory.getId(), LV_Inventory.getId(), MV_Inventory.getId(),
+ HV_Inventory.getId(), EV_Inventory.getId(), IV_Inventory.getId(), LuV_Inventory.getId(),
+ ZPM_Inventory.getId(), UV_Inventory.getId(), UHV_Inventory.getId(), UEV_Inventory.getId(),
+ UIV_Inventory.getId(), UMV_Inventory.getId(), UXV_Inventory.getId(), MAX_Inventory.getId()),
+
+ Tank(UPGRADE_CASING_REGISTRY_NAME, ULV_Tank.getId(), LV_Tank.getId(), MV_Tank.getId(), HV_Tank.getId(),
+ EV_Tank.getId(), IV_Tank.getId(), LuV_Tank.getId(), ZPM_Tank.getId(), UV_Tank.getId(), UHV_Tank.getId(),
+ UEV_Tank.getId(), UIV_Tank.getId(), UMV_Tank.getId(), UXV_Tank.getId(), MAX_Tank.getId()),
+
+ Amperage(UPGRADE_CASING_REGISTRY_NAME, Amp_4.getId(), Amp_16.getId(), Amp_64.getId(), Amp_256.getId(),
+ Amp_1_024.getId(), Amp_4_096.getId(), Amp_16_384.getId(), Amp_65_536.getId(), Amp_262_144.getId(),
+ Amp_1_048_576.getId()),
+
+ Laser(UPGRADE_CASING_REGISTRY_NAME, GT_MultiTileUpgradeCasing.Laser.getId()),
+
+ Wireless(UPGRADE_CASING_REGISTRY_NAME, GT_MultiTileUpgradeCasing.Wireless.getId()),
+
+ Cleanroom(UPGRADE_CASING_REGISTRY_NAME, GT_MultiTileUpgradeCasing.Cleanroom.getId()),
+
+ Heater(UPGRADE_CASING_REGISTRY_NAME, Heater_Prototype.getId(), Heater_IndustrialGrade.getId(),
+ Heater_NextGen.getId(), Heater_Omnipotent.getId(), Heater_OmegaType.getId()),
+
+ Insulator(UPGRADE_CASING_REGISTRY_NAME, Insulator_Prototype.getId(), Insulator_IndustrialGrade.getId(),
+ Insulator_NextGen.getId(), Insulator_Omnipotent.getId(), Insulator_OmegaType.getId());
+
+ private final MuTEStructureCasing casing;
+
+ UpgradeCasings(String registryName, Integer... validIds) {
+ casing = createMuTEStructureCasing(registryName, validIds);
+ }
+
+ public MuTEStructureCasing getCasing() {
+ return casing;
+ }
+ }
+
+ /**
+ * Specify all casing sets that are valid for a multiblock structure position. The first casing will be used as
+ * default when doing auto place
+ *
+ * @param modes Allowed modes on the casings
+ * @param validCasings Allowed casing sets
+ * @return Structure Element
+ * @param <T> Multiblock class
+ */
+ public static <T> IStructureElement<T> ofMuTECasings(int modes, MuTEStructureCasing... validCasings) {
+ if (validCasings == null || validCasings.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ return new IStructureElement<>() {
+
+ final MuTEStructureCasing[] allowedCasings = validCasings;
+ private final static short[] DEFAULT = new short[] { 255, 255, 255, 0 };
+ private static IIcon[] mIcons = null;
+
+ @Override
+ public boolean check(T t, World world, int x, int y, int z) {
+ final TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (!(tileEntity instanceof MultiBlockPart part)) return false;
+
+ for (MuTEStructureCasing casing : allowedCasings) {
+ if (casing.isCasingValid(part.getMultiTileEntityRegistryID(), part.getMultiTileEntityID())) {
+ final IMultiBlockController tTarget = part.getTarget(false);
+ if (tTarget != null && tTarget != t) return false;
+
+ part.setTarget((IMultiBlockController) t, modes);
+
+ ((Controller<?, ?>) t).registerSpecialCasings(part);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
+ // Moved here from Controller. TODO: Proper implementation
+ if (mIcons == null) {
+ mIcons = new IIcon[6];
+ Arrays.fill(mIcons, TextureSet.SET_NONE.mTextures[OrePrefixes.block.mTextureIndex].getIcon());
+ }
+ final short[] RGBA = DEFAULT;
+ StructureLibAPI.hintParticleTinted(world, x, y, z, mIcons, RGBA);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry
+ .getRegistry(validCasings[0].getRegistryId());
+ if (tRegistry == null) {
+ GT_FML_LOGGER.error("NULL REGISTRY");
+ return false;
+ }
+ final MultiTileEntityContainer tContainer = tRegistry
+ .getNewTileEntityContainer(world, x, y, z, validCasings[0].defaultMeta, null);
+ if (tContainer == null) {
+ GT_FML_LOGGER.error("NULL CONTAINER");
+ return false;
+ }
+ final IMultiTileEntity te = ((IMultiTileEntity) tContainer.mTileEntity);
+ if (!(te instanceof MultiBlockPart)) {
+ GT_FML_LOGGER.error("Not a multiblock part");
+ return false;
+ }
+ if (world.setBlock(x, y, z, tContainer.mBlock, 15 - tContainer.mBlockMetaData, 2)) {
+ tContainer.setMultiTile(world, x, y, z);
+ ((MultiBlockPart) te).setTarget((IMultiBlockController) t, modes);
+
+ ((Controller<?, ?>) t).registerSpecialCasings((MultiBlockPart) te);
+ }
+
+ return false;
+ }
+ };
+ }
+
+ public static MuTEStructureCasing createMuTEStructureCasing(String registryName, Integer... validIds) {
+ return new MuTEStructureCasing(registryName, validIds);
+ }
+
+ /**
+ * Object used to store a set of casings (e.g. all motor casings)
+ */
+ public static class MuTEStructureCasing {
+
+ private String registryName;
+ private int registryId = GT_Values.W;
+ private final int defaultMeta;
+ private final Integer[] validIds;
+
+ public MuTEStructureCasing(String registryName, Integer... validIds) {
+ MultiTileEntityRegistry registry = MultiTileEntityRegistry.getRegistry(registryName);
+ if (validIds == null || validIds.length == 0 || registry == null) {
+ throw new IllegalArgumentException();
+ }
+ this.registryName = registryName;
+ this.validIds = validIds;
+ this.defaultMeta = validIds[0];
+ }
+
+ public boolean isCasingValid(int registryId, int id) {
+ if (getRegistryId() != registryId) {
+ return false;
+ }
+ for (Integer validId : validIds) {
+ if (validId == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getDefaultMeta() {
+ return defaultMeta;
+ }
+
+ public int getRegistryId() {
+ // TODO: MuTE registry seems to somehow shift, probably due to NBT shenanigans. Lazy init circumvents this
+ // but it should be properly fixed in the future
+ if (registryId == GT_Values.W) {
+ MultiTileEntityRegistry registry = MultiTileEntityRegistry.getRegistry(registryName);
+ registryId = Block.getIdFromBlock(registry.mBlock);
+ }
+ return registryId;
+ }
+ }
+}
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..4263b77be6
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ToolHarvestHelper.java
@@ -0,0 +1,71 @@
+package gregtech.api.util;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+
+import ic2.core.block.BlockMultiID;
+import ic2.core.block.BlockScaffold;
+import ic2.core.block.machine.BlockMiningPipe;
+import ic2.core.block.machine.BlockMiningTip;
+import ic2.core.block.wiring.BlockCable;
+import ic2.core.crop.BlockCrop;
+
+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.isEmpty();
+ }
+
+ public static boolean isIC2Wrenchable(Block block) {
+ return (block instanceof BlockMultiID && !(block instanceof BlockCable) && !(block instanceof BlockCrop))
+ || block instanceof BlockScaffold
+ || block instanceof BlockMiningPipe
+ || block instanceof BlockMiningTip;
+ }
+
+ 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_TooltipDataCache.java b/src/main/java/gregtech/api/util/GT_TooltipDataCache.java
new file mode 100644
index 0000000000..431ef34fa4
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_TooltipDataCache.java
@@ -0,0 +1,105 @@
+package gregtech.api.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.minecraft.util.StatCollector;
+
+import gregtech.GT_Mod;
+
+public class GT_TooltipDataCache {
+
+ public static class TooltipData {
+
+ public List<String> text;
+ public List<String> shiftText;
+
+ public TooltipData(List<String> text, List<String> shiftText) {
+ this.text = text;
+ this.shiftText = shiftText;
+ }
+ }
+
+ private final Map<String, TooltipData> fetchedTooltipData = new HashMap<>();
+
+ /**
+ * Returns tooltip data respecting the user's configured verbosity levels, applying any formatting arguments.
+ *
+ * @param key the key to lookup
+ * @param args arguments for string formatting (prefer using positional arguments)
+ * @return The tooltip data the user asked for
+ */
+ public TooltipData getData(String key, Object... args) {
+ TooltipData tooltipData = fetchedTooltipData.get(key);
+ if (tooltipData == null) {
+ tooltipData = getUncachedTooltipData(key, args);
+ fetchedTooltipData.put(key, tooltipData);
+ }
+ return tooltipData;
+ }
+
+ /**
+ * Builds tooltip data respecting the user's configured verbosity levels, applying any formatting arguments.
+ *
+ * @param key the key to lookup
+ * @param args arguments for string formatting (prefer using positional arguments)
+ * @return The tooltip data the user asked for
+ */
+ public TooltipData getUncachedTooltipData(String key, Object... args) {
+ List<String> lines = getAllLines(key, args);
+ int normalLines = lines.size();
+ if (Math.max(GT_Mod.gregtechproxy.mTooltipVerbosity, GT_Mod.gregtechproxy.mTooltipShiftVerbosity) >= 3) {
+ lines.addAll(getAllLines(key + ".extended", args)); // Are extended lines enabled? If so add them to the
+ // lines
+ }
+ if (lines.size() == 0) {
+ lines.add(key); // Fallback in case no lines could be found at all
+ }
+ return new TooltipData(
+ lines.subList(0, getVerbosityIndex(GT_Mod.gregtechproxy.mTooltipVerbosity, normalLines, lines.size())),
+ lines
+ .subList(0, getVerbosityIndex(GT_Mod.gregtechproxy.mTooltipShiftVerbosity, normalLines, lines.size())));
+ }
+
+ /**
+ * Gets all the lines for the given key and every other subsequent consecutive key with a .n suffix, n in {1,2,3...}
+ *
+ * @param key the key to lookup
+ * @param args arguments for string formatting (prefer using positional arguments)
+ * @return The lines for the key and all of it's subkeys
+ */
+ private List<String> getAllLines(String key, Object... args) {
+ List<String> lines = new ArrayList<>();
+ String keyToLookup = key;
+ int i = 1; // First loop has no .number postfix
+ while (StatCollector.canTranslate(keyToLookup)) {
+ lines.add(StatCollector.translateToLocalFormatted(keyToLookup, args));
+ keyToLookup = key + "." + i++;
+ }
+ return lines;
+ }
+
+ /**
+ * Determines how many lines from a tooltip to include from the full line list to respect a given verbosity level.
+ *
+ * @param tooltipVerbosity the verbosity level we're applying
+ * @param defaultIndex return if tooltipVerbosity is 2
+ * @param maxIndex return if tooltipVerbosity is greater than 2
+ * @return verbosity appropriate index
+ */
+ private static int getVerbosityIndex(int tooltipVerbosity, int defaultIndex, int maxIndex) {
+ int index;
+ if (tooltipVerbosity < 1) {
+ index = 0;
+ } else if (tooltipVerbosity == 1) {
+ index = 1;
+ } else if (tooltipVerbosity == 2) {
+ index = defaultIndex;
+ } else {
+ index = maxIndex;
+ }
+ return index;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Util.java b/src/main/java/gregtech/api/util/GT_Util.java
new file mode 100644
index 0000000000..8a799a9616
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Util.java
@@ -0,0 +1,202 @@
+package gregtech.api.util;
+
+import net.minecraft.block.Block;
+import net.minecraft.init.Blocks;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraft.util.Tuple;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.Chunk;
+
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+
+public class GT_Util {
+
+ // Last broken tile entity
+ public static final ThreadLocal<TileEntity> LAST_BROKEN_TILEENTITY = new ThreadLocal<>();
+
+ public static Tuple tuple(String key, Object value) {
+ return new Tuple(key, value);
+ }
+
+ public static NBTTagCompound fuseNBT(NBTTagCompound aNBT1, NBTTagCompound aNBT2) {
+ if (aNBT1 == null) return aNBT2 == null ? new NBTTagCompound() : (NBTTagCompound) aNBT2.copy();
+ final NBTTagCompound rNBT = (NBTTagCompound) aNBT1.copy();
+ if (aNBT2 == null) return rNBT;
+ for (Object tKey : aNBT2.func_150296_c /* getKeySet */())
+ if (!rNBT.hasKey(tKey.toString())) rNBT.setTag(tKey.toString(), aNBT2.getTag(tKey.toString()));
+ return rNBT;
+ }
+
+ /**
+ * Construct a NBTTagCompound from a series of key, value pairs. Inspired from GT6.
+ */
+ public static NBTTagCompound makeNBT(Tuple... aTags) {
+ final NBTTagCompound rNBT = new NBTTagCompound();
+ for (Tuple t : aTags) {
+ if (t.getSecond() == null) continue;
+
+ if (t.getSecond() instanceof Boolean) rNBT.setBoolean(
+ t.getFirst()
+ .toString(),
+ (Boolean) t.getSecond());
+ else if (t.getSecond() instanceof Byte) rNBT.setByte(
+ t.getFirst()
+ .toString(),
+ (Byte) t.getSecond());
+ else if (t.getSecond() instanceof Short) rNBT.setShort(
+ t.getFirst()
+ .toString(),
+ (Short) t.getSecond());
+ else if (t.getSecond() instanceof Integer) rNBT.setInteger(
+ t.getFirst()
+ .toString(),
+ (Integer) t.getSecond());
+ else if (t.getSecond() instanceof Long) rNBT.setLong(
+ t.getFirst()
+ .toString(),
+ (Long) t.getSecond());
+ else if (t.getSecond() instanceof Float) rNBT.setFloat(
+ t.getFirst()
+ .toString(),
+ (Float) t.getSecond());
+ else if (t.getSecond() instanceof Double) rNBT.setDouble(
+ t.getFirst()
+ .toString(),
+ (Double) t.getSecond());
+ else if (t.getSecond() instanceof String) rNBT.setString(
+ t.getFirst()
+ .toString(),
+ (String) t.getSecond());
+ else if (t.getSecond() instanceof NBTBase) rNBT.setTag(
+ t.getFirst()
+ .toString(),
+ (NBTBase) t.getSecond());
+ else rNBT.setString(
+ t.getFirst()
+ .toString(),
+ t.getSecond()
+ .toString());
+ }
+
+ return rNBT;
+ }
+
+ /**
+ * Get a TileEntity
+ */
+ public static TileEntity getTileEntity(World aWorld, int aX, int aY, int aZ, boolean aLoadUnloadedChunks) {
+ if (aLoadUnloadedChunks || aWorld.blockExists(aX, aY, aZ)) {
+ TileEntity rTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (rTileEntity instanceof IMultiTileEntity && ((IMultiTileEntity) rTileEntity).isDead()) return null;
+ if (rTileEntity != null) return rTileEntity;
+ rTileEntity = LAST_BROKEN_TILEENTITY.get();
+ if (rTileEntity != null && rTileEntity.xCoord == aX && rTileEntity.yCoord == aY && rTileEntity.zCoord == aZ)
+ return rTileEntity;
+ }
+ return null;
+ }
+
+ /** Sets the TileEntity at the passed position, with the option of turning adjacent TileEntity updates off. */
+ public static TileEntity setTileEntity(World aWorld, int aX, int aY, int aZ, TileEntity aTileEntity,
+ boolean aCauseTileEntityUpdates) {
+ if (aCauseTileEntityUpdates) aWorld.setTileEntity(aX, aY, aZ, aTileEntity);
+ else {
+ Chunk tChunk = aWorld.getChunkFromChunkCoords(aX >> 4, aZ >> 4);
+ if (tChunk != null) {
+ aWorld.addTileEntity(aTileEntity);
+ tChunk.func_150812_a /* setBlockTileEntityInChunk */(aX & 15, aY, aZ & 15, aTileEntity);
+ tChunk.setChunkModified();
+ }
+ }
+ return aTileEntity;
+ }
+
+ public static boolean setTileEntity(World aWorld, int aX, int aY, int aZ, Block aBlock, short aMeta, long aFlags,
+ boolean aRemoveGrassBelow) {
+ if (aRemoveGrassBelow) {
+ final Block tBlock = aWorld.getBlock(aX, aY - 1, aZ);
+ if (tBlock == Blocks.grass || tBlock == Blocks.mycelium)
+ aWorld.setBlock(aX, aY - 1, aZ, Blocks.dirt, 0, (byte) aFlags);
+ }
+ return aWorld.setBlock(aX, aY, aZ, aBlock, aMeta, (byte) aFlags);
+ }
+
+ public static TileEntity getTileEntity(World aWorld, ChunkCoordinates aCoords, boolean aLoadUnloadedChunks) {
+ return getTileEntity(aWorld, aCoords.posX, aCoords.posY, aCoords.posZ, aLoadUnloadedChunks);
+ }
+
+ /** Marks a Chunk dirty so it is saved */
+ public static boolean markChunkDirty(World aWorld, int aX, int aZ) {
+ if (aWorld == null || aWorld.isRemote) return false;
+ Chunk aChunk = aWorld.getChunkFromBlockCoords(aX, aZ);
+ if (aChunk == null) {
+ aWorld.getBlockMetadata(aX, 0, aZ);
+ aChunk = aWorld.getChunkFromBlockCoords(aX, aZ);
+ if (aChunk == null) {
+ GT_Log.err.println(
+ "Some important Chunk does not exist for some reason at Coordinates X: " + aX + " and Z: " + aZ);
+ return false;
+ }
+ }
+ aChunk.setChunkModified();
+ return true;
+ }
+
+ /** Marks a Chunk dirty so it is saved */
+ public static boolean markChunkDirty(Object aTileEntity) {
+ return aTileEntity instanceof TileEntity && markChunkDirty(
+ ((TileEntity) aTileEntity).getWorldObj(),
+ ((TileEntity) aTileEntity).xCoord,
+ ((TileEntity) aTileEntity).zCoord);
+ }
+
+ public static int mixRGBInt(int aRGB1, int aRGB2) {
+ return getRGBInt(
+ new short[] { (short) ((getR(aRGB1) + getR(aRGB2)) >> 1), (short) ((getG(aRGB1) + getG(aRGB2)) >> 1),
+ (short) ((getB(aRGB1) + getB(aRGB2)) >> 1) });
+ }
+
+ public static int getRGBInt(short[] aColors) {
+ return aColors == null ? 16777215 : (aColors[0] << 16) | (aColors[1] << 8) | aColors[2];
+ }
+
+ public static int getRGBaInt(short[] aColors) {
+ return aColors == null ? 16777215 : (aColors[0]) << 16 | (aColors[1] << 8) | aColors[2] | (aColors[3] << 24);
+ }
+
+ public static String toHexString(short[] aColors) {
+ return aColors == null ? "FFFFFF" : Integer.toHexString((aColors[0] << 16) | (aColors[1] << 8) | aColors[2]);
+ }
+
+ public static int getRGBInt(short aR, short aG, short aB) {
+ return (aR << 16) | (aG << 8) | aB;
+ }
+
+ public static int getRGBaInt(short aR, short aG, short aB, short aA) {
+ return (aR << 16) | (aG << 8) | aB | (aA << 24);
+ }
+
+ public static short[] getRGBaArray(int aColors) {
+ return new short[] { (short) ((aColors >>> 16) & 255), (short) ((aColors >>> 8) & 255), (short) (aColors & 255),
+ (short) ((aColors >>> 24) & 255) };
+ }
+
+ public static short getR(int aColors) {
+ return (short) ((aColors >>> 16) & 255);
+ }
+
+ public static short getG(int aColors) {
+ return (short) ((aColors >>> 8) & 255);
+ }
+
+ public static short getB(int aColors) {
+ return (short) (aColors & 255);
+ }
+
+ public static short getA(int aColors) {
+ return (short) ((aColors >>> 24) & 255);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Utility.java b/src/main/java/gregtech/api/util/GT_Utility.java
new file mode 100644
index 0000000000..62c4498927
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Utility.java
@@ -0,0 +1,4982 @@
+package gregtech.api.util;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.enums.GT_Values.D1;
+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.api.enums.Materials.FLUID_MAP;
+import static gregtech.api.enums.Mods.Translocator;
+import static gregtech.common.GT_UndergroundOil.undergroundOilReadInformation;
+import static net.minecraftforge.common.util.ForgeDirection.DOWN;
+import static net.minecraftforge.common.util.ForgeDirection.EAST;
+import static net.minecraftforge.common.util.ForgeDirection.NORTH;
+import static net.minecraftforge.common.util.ForgeDirection.SOUTH;
+import static net.minecraftforge.common.util.ForgeDirection.UNKNOWN;
+import static net.minecraftforge.common.util.ForgeDirection.UP;
+import static net.minecraftforge.common.util.ForgeDirection.WEST;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.enchantment.EnchantmentHelper;
+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;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.ISidedInventory;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTBase.NBTPrimitive;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.nbt.NBTTagString;
+import net.minecraft.network.play.server.S07PacketRespawn;
+import net.minecraft.network.play.server.S1DPacketEntityEffect;
+import net.minecraft.network.play.server.S1FPacketSetExperience;
+import net.minecraft.potion.Potion;
+import net.minecraft.potion.PotionEffect;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityChest;
+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.util.MovingObjectPosition;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.Vec3;
+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;
+import net.minecraftforge.common.util.Constants;
+import net.minecraftforge.common.util.FakePlayer;
+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.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 com.google.auto.value.AutoValue;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.SetMultimap;
+import com.gtnewhorizon.structurelib.alignment.IAlignment;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
+import com.mojang.authlib.GameProfile;
+
+import buildcraft.api.transport.IPipeTile;
+import cofh.api.energy.IEnergyReceiver;
+import cofh.api.transport.IItemDuct;
+import cpw.mods.fml.common.FMLCommonHandler;
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.GregTech_API;
+import gregtech.api.damagesources.GT_DamageSources;
+import gregtech.api.damagesources.GT_DamageSources.DamageSourceHotItem;
+import gregtech.api.enchants.Enchantment_Hazmat;
+import gregtech.api.enchants.Enchantment_Radioactivity;
+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.SoundResource;
+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.IHasIndexedTexture;
+import gregtech.api.interfaces.IProjectileItem;
+import gregtech.api.interfaces.ITexture;
+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;
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.net.GT_Packet_Sound;
+import gregtech.api.objects.CollectorUtils;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.objects.GT_ItemStack2;
+import gregtech.api.objects.ItemData;
+import gregtech.api.recipe.RecipeMaps;
+import gregtech.api.threads.GT_Runnable_Sound;
+import gregtech.api.util.extensions.ArrayExt;
+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;
+import ic2.api.recipe.RecipeOutput;
+
+/**
+ * NEVER INCLUDE THIS FILE IN YOUR MOD!!!
+ * <p/>
+ * Just a few Utility Functions I use.
+ */
+public class GT_Utility {
+
+ /**
+ * Formats a number with group separator and at most 2 fraction digits.
+ */
+ private static final Map<Locale, DecimalFormat> decimalFormatters = new HashMap<>();
+
+ /**
+ * Forge screwed the Fluid Registry up again, so I make my own, which is also much more efficient than the stupid
+ * Stuff over there.
+ */
+ 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<String, FluidContainerData>> sEmptyContainerToFluidToData = new HashMap<>();
+ private static final Map<String, 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<>();
+
+ private static final Map<Integer, Boolean> sOreTable = new HashMap<>();
+ 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<>();
+ private static int sBookCount = 0;
+ public static UUID defaultUuid = null; // maybe default non-null?
+ // UUID.fromString("00000000-0000-0000-0000-000000000000");
+
+ static {
+ 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) {
+ return number > Integer.MAX_VALUE - margin ? Integer.MAX_VALUE - margin : (int) number;
+ }
+
+ public static int safeInt(long number) {
+ return number > V[V.length - 1] ? safeInt(V[V.length - 1], 1)
+ : number < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) number;
+ }
+
+ public static Field getPublicField(Object aObject, String aField) {
+ Field rField = null;
+ try {
+ rField = aObject.getClass()
+ .getDeclaredField(aField);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return rField;
+ }
+
+ public static Field getField(Object aObject, String aField) {
+ Field rField = null;
+ try {
+ rField = aObject.getClass()
+ .getDeclaredField(aField);
+ rField.setAccessible(true);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return rField;
+ }
+
+ public static Field getField(Class<?> aObject, String aField) {
+ Field rField = null;
+ try {
+ rField = aObject.getDeclaredField(aField);
+ rField.setAccessible(true);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return rField;
+ }
+
+ public static Method getMethod(Class<?> aObject, String aMethod, Class<?>... aParameterTypes) {
+ Method rMethod = null;
+ try {
+ rMethod = aObject.getMethod(aMethod, aParameterTypes);
+ rMethod.setAccessible(true);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return rMethod;
+ }
+
+ public static Method getMethod(Object aObject, String aMethod, Class<?>... aParameterTypes) {
+ Method rMethod = null;
+ try {
+ rMethod = aObject.getClass()
+ .getMethod(aMethod, aParameterTypes);
+ rMethod.setAccessible(true);
+ } catch (Throwable e) {
+ /* Do nothing */
+ }
+ return rMethod;
+ }
+
+ public static Field getField(Object aObject, String aField, boolean aPrivate, boolean aLogErrors) {
+ try {
+ Field tField = (aObject instanceof Class) ? ((Class<?>) aObject).getDeclaredField(aField)
+ : (aObject instanceof String) ? Class.forName((String) aObject)
+ .getDeclaredField(aField)
+ : aObject.getClass()
+ .getDeclaredField(aField);
+ if (aPrivate) tField.setAccessible(true);
+ return tField;
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ return null;
+ }
+
+ public static Object getFieldContent(Object aObject, String aField, boolean aPrivate, boolean aLogErrors) {
+ try {
+ Field tField = (aObject instanceof Class) ? ((Class<?>) aObject).getDeclaredField(aField)
+ : (aObject instanceof String) ? Class.forName((String) aObject)
+ .getDeclaredField(aField)
+ : aObject.getClass()
+ .getDeclaredField(aField);
+ if (aPrivate) tField.setAccessible(true);
+ return tField.get(aObject instanceof Class || aObject instanceof String ? null : aObject);
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ return null;
+ }
+
+ public static Object callPublicMethod(Object aObject, String aMethod, Object... aParameters) {
+ return callMethod(aObject, aMethod, false, false, true, aParameters);
+ }
+
+ public static Object callPrivateMethod(Object aObject, String aMethod, Object... aParameters) {
+ return callMethod(aObject, aMethod, true, false, true, aParameters);
+ }
+
+ public static Object callMethod(Object aObject, String aMethod, boolean aPrivate, boolean aUseUpperCasedDataTypes,
+ boolean aLogErrors, Object... aParameters) {
+ try {
+ Class<?>[] tParameterTypes = new Class<?>[aParameters.length];
+ for (byte i = 0; i < aParameters.length; i++) {
+ if (aParameters[i] instanceof Class) {
+ tParameterTypes[i] = (Class<?>) aParameters[i];
+ aParameters[i] = null;
+ } else {
+ tParameterTypes[i] = aParameters[i].getClass();
+ }
+ if (!aUseUpperCasedDataTypes) {
+ if (tParameterTypes[i] == Boolean.class) tParameterTypes[i] = boolean.class;
+ else if (tParameterTypes[i] == Byte.class) tParameterTypes[i] = byte.class;
+ else if (tParameterTypes[i] == Short.class) tParameterTypes[i] = short.class;
+ else if (tParameterTypes[i] == Integer.class) tParameterTypes[i] = int.class;
+ else if (tParameterTypes[i] == Long.class) tParameterTypes[i] = long.class;
+ else if (tParameterTypes[i] == Float.class) tParameterTypes[i] = float.class;
+ else if (tParameterTypes[i] == Double.class) tParameterTypes[i] = double.class;
+ }
+ }
+
+ Method tMethod = (aObject instanceof Class) ? ((Class<?>) aObject).getMethod(aMethod, tParameterTypes)
+ : aObject.getClass()
+ .getMethod(aMethod, tParameterTypes);
+ if (aPrivate) tMethod.setAccessible(true);
+ return tMethod.invoke(aObject, aParameters);
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ return null;
+ }
+
+ public static Object callConstructor(String aClass, int aConstructorIndex, Object aReplacementObject,
+ boolean aLogErrors, Object... aParameters) {
+ try {
+ return callConstructor(
+ Class.forName(aClass),
+ aConstructorIndex,
+ aReplacementObject,
+ aLogErrors,
+ aParameters);
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ return aReplacementObject;
+ }
+
+ public static Object callConstructor(Class<?> aClass, int aConstructorIndex, Object aReplacementObject,
+ boolean aLogErrors, Object... aParameters) {
+ if (aConstructorIndex < 0) {
+ try {
+ for (Constructor<?> tConstructor : aClass.getConstructors()) {
+ try {
+ return tConstructor.newInstance(aParameters);
+ } catch (Throwable ignored) {}
+ }
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ } else {
+ try {
+ return aClass.getConstructors()[aConstructorIndex].newInstance(aParameters);
+ } catch (Throwable e) {
+ if (aLogErrors) e.printStackTrace(GT_Log.err);
+ }
+ }
+ return aReplacementObject;
+ }
+
+ public static String capitalizeString(String aString) {
+ if (aString != null && aString.length() > 0) return aString.substring(0, 1)
+ .toUpperCase() + aString.substring(1);
+ return E;
+ }
+
+ public static boolean getPotion(EntityLivingBase aPlayer, int aPotionIndex) {
+ try {
+ Field tPotionHashmap = null;
+
+ Field[] fields = EntityLiving.class.getDeclaredFields();
+
+ for (Field field : fields) {
+ if (field.getType() == HashMap.class) {
+ tPotionHashmap = field;
+ tPotionHashmap.setAccessible(true);
+ break;
+ }
+ }
+
+ if (tPotionHashmap != null) return ((HashMap<?, ?>) tPotionHashmap.get(aPlayer)).get(aPotionIndex) != null;
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return false;
+ }
+
+ public static String getClassName(Object aObject) {
+ if (aObject == null) return "null";
+ return aObject.getClass()
+ .getName()
+ .substring(
+ aObject.getClass()
+ .getName()
+ .lastIndexOf(".") + 1);
+ }
+
+ public static void removePotion(EntityLivingBase aPlayer, int aPotionIndex) {
+ try {
+ Field tPotionHashmap = null;
+
+ Field[] fields = EntityLiving.class.getDeclaredFields();
+
+ for (Field field : fields) {
+ if (field.getType() == HashMap.class) {
+ tPotionHashmap = field;
+ tPotionHashmap.setAccessible(true);
+ break;
+ }
+ }
+
+ if (tPotionHashmap != null) ((HashMap<?, ?>) tPotionHashmap.get(aPlayer)).remove(aPotionIndex);
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ public static boolean getFullInvisibility(EntityPlayer aPlayer) {
+ try {
+ if (aPlayer.isInvisible()) {
+ for (int i = 0; i < 4; i++) {
+ if (aPlayer.inventory.armorInventory[i] != null) {
+ if (aPlayer.inventory.armorInventory[i].getItem() instanceof GT_EnergyArmor_Item) {
+ if ((((GT_EnergyArmor_Item) aPlayer.inventory.armorInventory[i].getItem()).mSpecials & 512)
+ != 0) {
+ if (GT_ModHandler.canUseElectricItem(aPlayer.inventory.armorInventory[i], 10000)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return false;
+ }
+
+ public static ItemStack suckOneItemStackAt(World aWorld, double aX, double aY, double aZ, double aL, double aH,
+ double aW) {
+ for (EntityItem tItem : aWorld.getEntitiesWithinAABB(
+ EntityItem.class,
+ AxisAlignedBB.getBoundingBox(aX, aY, aZ, aX + aL, aY + aH, aZ + aW))) {
+ if (!tItem.isDead) {
+ aWorld.removeEntity(tItem);
+ tItem.setDead();
+ return tItem.getEntityItem();
+ }
+ }
+ return null;
+ }
+
+ public static byte getOppositeSide(ForgeDirection side) {
+ return (byte) side.getOpposite()
+ .ordinal();
+ }
+
+ public static byte getTier(long l) {
+ byte i = -1;
+ while (++i < V.length) if (l <= V[i]) return i;
+ return (byte) (V.length - 1);
+ }
+
+ public static long getAmperageForTier(long voltage, byte tier) {
+ return ceilDiv(voltage, GT_Values.V[tier]);
+ }
+
+ /**
+ * Rounds up partial voltage that exceeds tiered voltage, e.g. 4,096 -> 8,192(IV)
+ */
+ public static long roundUpVoltage(long voltage) {
+ if (voltage > V[V.length - 1]) {
+ return voltage;
+ }
+ return V[GT_Utility.getTier(voltage)];
+ }
+
+ public static String getColoredTierNameFromVoltage(long voltage) {
+ return getColoredTierNameFromTier(getTier(voltage));
+ }
+
+ public static String getColoredTierNameFromTier(byte tier) {
+ return GT_Values.TIER_COLORS[tier] + GT_Values.VN[tier] + EnumChatFormatting.RESET;
+ }
+
+ /**
+ * @return e.g. {@code " (LV)"}
+ */
+ @Nonnull
+ public static String getTierNameWithParentheses(long voltage) {
+ byte tier = getTier(voltage);
+ if (tier < 0) {
+ return "";
+ } else if (tier >= GT_Values.VN.length - 1) {
+ return " (MAX+)";
+ }
+ return " (" + GT_Values.VN[tier] + ")";
+ }
+
+ public static void sendChatToPlayer(EntityPlayer aPlayer, String aChatMessage) {
+ if (aPlayer instanceof EntityPlayerMP && aChatMessage != null) {
+ aPlayer.addChatComponentMessage(new ChatComponentText(aChatMessage));
+ }
+ }
+
+ public static void checkAvailabilities() {
+ if (CHECK_ALL) {
+ try {
+ Class<IItemDuct> tClass = IItemDuct.class;
+ tClass.getCanonicalName();
+ TE_CHECK = true;
+ } catch (Throwable e) {
+ /**/
+ }
+ try {
+ Class<IPipeTile> tClass = buildcraft.api.transport.IPipeTile.class;
+ tClass.getCanonicalName();
+ BC_CHECK = true;
+ } catch (Throwable e) {
+ /**/
+ }
+ try {
+ Class<IEnergyReceiver> tClass = cofh.api.energy.IEnergyReceiver.class;
+ tClass.getCanonicalName();
+ RF_CHECK = true;
+ } catch (Throwable e) {
+ /**/
+ }
+ CHECK_ALL = false;
+ }
+ }
+
+ public static boolean isConnectableNonInventoryPipe(TileEntity tileEntity, ForgeDirection side) {
+ if (tileEntity == null) return false;
+ checkAvailabilities();
+ if (TE_CHECK && tileEntity instanceof IItemDuct) return true;
+ if (BC_CHECK && tileEntity instanceof buildcraft.api.transport.IPipeTile pipeTile)
+ return pipeTile.isPipeConnected(side);
+ return Translocator.isModLoaded() && tileEntity instanceof codechicken.translocator.TileItemTranslocator;
+ }
+
+ /**
+ * Moves Stack from Inv-Slot to Inv-Slot, without checking if its even allowed.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveStackIntoPipe(IInventory aTileEntity1, Object aTileEntity2, int[] aGrabSlots,
+ ForgeDirection fromSide, ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter,
+ byte aMaxTargetStackSize, byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ return moveStackIntoPipe(
+ aTileEntity1,
+ aTileEntity2,
+ aGrabSlots,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ true);
+ }
+
+ /**
+ * Moves Stack from Inv-Slot to Inv-Slot, without checking if it is even allowed.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveStackIntoPipe(IInventory fromInventory, Object toObject, int[] fromSlots,
+ ForgeDirection fromSide, ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter,
+ byte aMaxTargetStackSize, byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce,
+ boolean dropItem) {
+ if (fromInventory == null || aMaxTargetStackSize <= 0
+ || aMinTargetStackSize <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMaxMoveAtOnce <= 0
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+ if (toObject != null) {
+ checkAvailabilities();
+ if (TE_CHECK && toObject instanceof IItemDuct itemDuct) {
+ for (final int aGrabSlot : fromSlots) {
+ if (listContainsItem(aFilter, fromInventory.getStackInSlot(aGrabSlot), true, aInvertFilter)) {
+ if (isAllowedToTakeFromSlot(
+ fromInventory,
+ aGrabSlot,
+ fromSide,
+ fromInventory.getStackInSlot(aGrabSlot))) {
+ if (Math.max(aMinMoveAtOnce, aMinTargetStackSize)
+ <= fromInventory.getStackInSlot(aGrabSlot).stackSize) {
+ ItemStack tStack = copyAmount(
+ Math.min(
+ fromInventory.getStackInSlot(aGrabSlot).stackSize,
+ Math.min(aMaxMoveAtOnce, aMaxTargetStackSize)),
+ fromInventory.getStackInSlot(aGrabSlot));
+ ItemStack rStack = itemDuct.insertItem(putSide, copyOrNull(tStack));
+ byte tMovedItemCount = (byte) (tStack.stackSize
+ - (rStack == null ? 0 : rStack.stackSize));
+ if (tMovedItemCount >= 1 /* Math.max(aMinMoveAtOnce, aMinTargetStackSize) */) {
+ fromInventory.decrStackSize(aGrabSlot, tMovedItemCount);
+ fromInventory.markDirty();
+ return tMovedItemCount;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ }
+ if (BC_CHECK && toObject instanceof buildcraft.api.transport.IPipeTile bcPipe) {
+ for (int fromSlot : fromSlots) {
+ if (listContainsItem(aFilter, fromInventory.getStackInSlot(fromSlot), true, aInvertFilter)) {
+ if (isAllowedToTakeFromSlot(
+ fromInventory,
+ fromSlot,
+ fromSide,
+ fromInventory.getStackInSlot(fromSlot))) {
+ if (Math.max(aMinMoveAtOnce, aMinTargetStackSize)
+ <= fromInventory.getStackInSlot(fromSlot).stackSize) {
+ ItemStack tStack = copyAmount(
+ Math.min(
+ fromInventory.getStackInSlot(fromSlot).stackSize,
+ Math.min(aMaxMoveAtOnce, aMaxTargetStackSize)),
+ fromInventory.getStackInSlot(fromSlot));
+ byte tMovedItemCount = (byte) bcPipe.injectItem(copyOrNull(tStack), false, putSide);
+ if (tMovedItemCount >= Math.max(aMinMoveAtOnce, aMinTargetStackSize)) {
+ tMovedItemCount = (byte) (bcPipe
+ .injectItem(copyAmount(tMovedItemCount, tStack), true, putSide));
+ fromInventory.decrStackSize(fromSlot, tMovedItemCount);
+ fromInventory.markDirty();
+ return tMovedItemCount;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ }
+ }
+
+ if (fromInventory instanceof TileEntity fromTileEntity && fromSide != ForgeDirection.UNKNOWN
+ && fromSide.getOpposite() == ForgeDirection.getOrientation(putSide.ordinal())) {
+ int tX = fromTileEntity.xCoord + fromSide.offsetX, tY = fromTileEntity.yCoord + fromSide.offsetY,
+ tZ = fromTileEntity.zCoord + fromSide.offsetZ;
+ if (!hasBlockHitBox(((TileEntity) fromInventory).getWorldObj(), tX, tY, tZ) && dropItem) {
+ for (final int fromSlot : fromSlots) {
+ if (listContainsItem(aFilter, fromInventory.getStackInSlot(fromSlot), true, aInvertFilter)) {
+ if (isAllowedToTakeFromSlot(
+ fromInventory,
+ fromSlot,
+ fromSide,
+ fromInventory.getStackInSlot(fromSlot))) {
+ if (Math.max(aMinMoveAtOnce, aMinTargetStackSize)
+ <= fromInventory.getStackInSlot(fromSlot).stackSize) {
+ final ItemStack tStack = copyAmount(
+ Math.min(
+ fromInventory.getStackInSlot(fromSlot).stackSize,
+ Math.min(aMaxMoveAtOnce, aMaxTargetStackSize)),
+ fromInventory.getStackInSlot(fromSlot));
+ final EntityItem tEntity = new EntityItem(
+ ((TileEntity) fromInventory).getWorldObj(),
+ tX + 0.5,
+ tY + 0.5,
+ tZ + 0.5,
+ tStack);
+ tEntity.motionX = tEntity.motionY = tEntity.motionZ = 0;
+ ((TileEntity) fromInventory).getWorldObj()
+ .spawnEntityInWorld(tEntity);
+ assert tStack != null;
+ fromInventory.decrStackSize(fromSlot, tStack.stackSize);
+ fromInventory.markDirty();
+ return (byte) tStack.stackSize;
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Moves Stack from Inv-Slot to Inv-Slot, without checking if its even allowed. (useful for internal Inventory
+ * Operations)
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveStackFromSlotAToSlotB(IInventory aTileEntity1, IInventory aTileEntity2, int aGrabFrom,
+ int aPutTo, byte aMaxTargetStackSize, byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ if (aTileEntity1 == null || aTileEntity2 == null
+ || aMinTargetStackSize <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMaxMoveAtOnce <= 0
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+
+ ItemStack tStack1 = aTileEntity1.getStackInSlot(aGrabFrom), tStack2 = aTileEntity2.getStackInSlot(aPutTo),
+ tStack3;
+ if (tStack1 != null) {
+ if (tStack2 != null && !areStacksEqual(tStack1, tStack2)) return 0;
+ tStack3 = copyOrNull(tStack1);
+ aMaxTargetStackSize = (byte) Math.min(
+ aMaxTargetStackSize,
+ Math.min(
+ tStack3.getMaxStackSize(),
+ Math.min(
+ tStack2 == null ? Integer.MAX_VALUE : tStack2.getMaxStackSize(),
+ aTileEntity2.getInventoryStackLimit())));
+ tStack3.stackSize = Math
+ .min(tStack3.stackSize, aMaxTargetStackSize - (tStack2 == null ? 0 : tStack2.stackSize));
+ if (tStack3.stackSize > aMaxMoveAtOnce) tStack3.stackSize = aMaxMoveAtOnce;
+ if (tStack3.stackSize + (tStack2 == null ? 0 : tStack2.stackSize)
+ >= Math.min(tStack3.getMaxStackSize(), aMinTargetStackSize) && tStack3.stackSize >= aMinMoveAtOnce) {
+ tStack3 = aTileEntity1.decrStackSize(aGrabFrom, tStack3.stackSize);
+ aTileEntity1.markDirty();
+ if (tStack3 != null) {
+ if (tStack2 == null) {
+ aTileEntity2.setInventorySlotContents(aPutTo, copyOrNull(tStack3));
+ } else {
+ tStack2.stackSize += tStack3.stackSize;
+ }
+ aTileEntity2.markDirty();
+ return (byte) tStack3.stackSize;
+ }
+ }
+ }
+ return 0;
+ }
+
+ public static boolean isAllowedToTakeFromSlot(IInventory aTileEntity, int aSlot, ForgeDirection side,
+ ItemStack aStack) {
+ if (side == ForgeDirection.UNKNOWN) {
+ return Arrays.stream(ForgeDirection.VALID_DIRECTIONS)
+ .anyMatch(d -> isAllowedToTakeFromSlot(aTileEntity, aSlot, d, aStack));
+ }
+ if (aTileEntity instanceof ISidedInventory sided) return sided.canExtractItem(aSlot, aStack, side.ordinal());
+ return true;
+ }
+
+ public static boolean isAllowedToPutIntoSlot(IInventory aTileEntity, int aSlot, ForgeDirection side,
+ ItemStack aStack, byte aMaxStackSize) {
+ ItemStack tStack = aTileEntity.getStackInSlot(aSlot);
+ if (tStack != null && (!areStacksEqual(tStack, aStack) || tStack.stackSize >= tStack.getMaxStackSize()))
+ return false;
+ if (side == ForgeDirection.UNKNOWN) {
+ return Arrays.stream(ForgeDirection.VALID_DIRECTIONS)
+ .anyMatch(d -> isAllowedToPutIntoSlot(aTileEntity, aSlot, d, aStack, aMaxStackSize));
+ }
+ if (aTileEntity instanceof ISidedInventory
+ && !((ISidedInventory) aTileEntity).canInsertItem(aSlot, aStack, side.ordinal())) return false;
+ return aSlot < aTileEntity.getSizeInventory() && aTileEntity.isItemValidForSlot(aSlot, aStack);
+ }
+
+ /**
+ * moves multiple stacks from Inv-Side to Inv-Side
+ *
+ * @return the Amount of moved Items
+ */
+ public static int moveMultipleItemStacks(Object aTileEntity1, Object aTileEntity2, ForgeDirection fromSide,
+ ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize,
+ byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce, int aStackAmount) {
+ if (aTileEntity1 instanceof IInventory) return moveMultipleItemStacks(
+ (IInventory) aTileEntity1,
+ aTileEntity2,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aStackAmount,
+ true);
+ return 0;
+ }
+
+ public static int moveMultipleItemStacks(IInventory fromInventory, Object toObject, ForgeDirection fromSide,
+ ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize,
+ byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce, int aMaxStackTransfer,
+ boolean aDoCheckChests) {
+ if (fromInventory == null || aMaxTargetStackSize <= 0
+ || aMinTargetStackSize <= 0
+ || aMaxMoveAtOnce <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMinMoveAtOnce > aMaxMoveAtOnce
+ || aMaxStackTransfer == 0) return 0;
+
+ // find where to take from
+ final int[] tGrabSlots = new int[fromInventory.getSizeInventory()];
+ int tGrabSlotsSize = 0;
+ if (fromInventory instanceof ISidedInventory) {
+ for (int i : ((ISidedInventory) fromInventory).getAccessibleSlotsFromSide(fromSide.ordinal())) {
+ final ItemStack s = fromInventory.getStackInSlot(i);
+ if (s == null || !isAllowedToTakeFromSlot(fromInventory, i, fromSide, s)
+ || s.stackSize < aMinMoveAtOnce
+ || !listContainsItem(aFilter, s, true, aInvertFilter)) continue;
+ tGrabSlots[tGrabSlotsSize++] = i;
+ }
+ } else {
+ for (int i = 0; i < tGrabSlots.length; i++) {
+ ItemStack s = fromInventory.getStackInSlot(i);
+ if (s == null || s.stackSize < aMinMoveAtOnce || !listContainsItem(aFilter, s, true, aInvertFilter))
+ continue;
+ tGrabSlots[tGrabSlotsSize++] = i;
+ }
+ }
+
+ // no source, bail out
+ if (tGrabSlotsSize == 0) {
+ // maybe source is a double chest. check it
+ if (aDoCheckChests && fromInventory instanceof TileEntityChest chest) return moveFromAdjacentChests(
+ chest,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer);
+ return 0;
+ }
+
+ // if target is an inventory, e.g. chest, machine, drawers...
+ if (toObject instanceof IInventory toInventory) {
+
+ // partially filled slot spare space mapping.
+ // value is the sum of all spare space left not counting completely empty slot
+ final HashMap<ItemId, Integer> tPutItems = new HashMap<>(toInventory.getSizeInventory());
+ // partially filled slot contents
+ final HashMap<ItemId, List<ItemStack>> tPutItemStacks = new HashMap<>(toInventory.getSizeInventory());
+ // completely empty slots
+ final List<Integer> tPutFreeSlots = new ArrayList<>(toInventory.getSizeInventory());
+
+ // find possible target slots
+ int[] accessibleSlots = null;
+ if (toObject instanceof ISidedInventory sided)
+ accessibleSlots = sided.getAccessibleSlotsFromSide(putSide.ordinal());
+ for (int i = 0; i < toInventory.getSizeInventory(); i++) {
+ int slot = i;
+ if (accessibleSlots != null) {
+ if (accessibleSlots.length <= i) break;
+ slot = accessibleSlots[slot];
+ }
+ ItemStack s = toInventory.getStackInSlot(slot);
+ if (s == null) {
+ tPutFreeSlots.add(slot);
+ } else if ((s.stackSize < s.getMaxStackSize() && s.stackSize < toInventory.getInventoryStackLimit())
+ && aMinMoveAtOnce <= s.getMaxStackSize() - s.stackSize
+ && isAllowedToPutIntoSlot(toInventory, slot, putSide, s, (byte) 64)) {
+ ItemId sID = ItemId.createNoCopy(s);
+ tPutItems.merge(
+ sID,
+ (Math.min(s.getMaxStackSize(), toInventory.getInventoryStackLimit()) - s.stackSize),
+ Integer::sum);
+ tPutItemStacks.computeIfAbsent(sID, k -> new ArrayList<>())
+ .add(s);
+ }
+ }
+
+ // target completely filled, bail out
+ if (tPutItems.isEmpty() && tPutFreeSlots.isEmpty()) {
+ // maybe target is a double chest. check it.
+ if (aDoCheckChests && toObject instanceof TileEntityChest chest) return moveToAdjacentChests(
+ fromInventory,
+ chest,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer);
+ return 0;
+ }
+
+ // go over source stacks one by one
+ int tStacksMoved = 0, tTotalItemsMoved = 0;
+ for (int j = 0; j < tGrabSlotsSize; j++) {
+ final int grabSlot = tGrabSlots[j];
+ int tMovedItems;
+ int tStackSize;
+ do {
+ tMovedItems = 0;
+ final ItemStack tGrabStack = fromInventory.getStackInSlot(grabSlot);
+ if (tGrabStack == null) break;
+ tStackSize = tGrabStack.stackSize;
+ final ItemId sID = ItemId.createNoCopy(tGrabStack);
+
+ if (tPutItems.containsKey(sID)) {
+ // there is a partially filled slot, try merging
+ final int canPut = Math.min(tPutItems.get(sID), aMaxMoveAtOnce);
+ if (canPut >= aMinMoveAtOnce) {
+ final List<ItemStack> putStack = tPutItemStacks.get(sID);
+ if (!putStack.isEmpty()) {
+ // can move, do merge
+ int toPut = Math.min(canPut, tStackSize);
+ tMovedItems = toPut;
+ for (int i = 0; i < putStack.size(); i++) {
+ final ItemStack s = putStack.get(i);
+ final int sToPut = Math.min(
+ Math.min(
+ Math.min(toPut, s.getMaxStackSize() - s.stackSize),
+ toInventory.getInventoryStackLimit() - s.stackSize),
+ aMaxTargetStackSize - s.stackSize);
+ if (sToPut <= 0) continue;
+ if (sToPut < aMinMoveAtOnce) continue;
+ if (s.stackSize + sToPut < aMinTargetStackSize) continue;
+ toPut -= sToPut;
+ s.stackSize += sToPut;
+ if (s.stackSize == s.getMaxStackSize()
+ || s.stackSize == toInventory.getInventoryStackLimit()) {
+ // this slot is full. remove this stack from candidate list
+ putStack.remove(i);
+ i--;
+ }
+ if (toPut == 0) break;
+ }
+ tMovedItems -= toPut;
+ if (tMovedItems > 0) {
+ tStackSize -= tMovedItems;
+ tTotalItemsMoved += tMovedItems;
+ // deduct spare space
+ tPutItems.merge(sID, tMovedItems, (a, b) -> a.equals(b) ? null : a - b);
+
+ if (tStackSize == 0) fromInventory.setInventorySlotContents(grabSlot, null);
+ else tGrabStack.stackSize = tStackSize;
+
+ fromInventory.markDirty();
+ toInventory.markDirty();
+ }
+ }
+ }
+ }
+ // still stuff to move & have completely empty slots
+ if (tStackSize > 0 && !tPutFreeSlots.isEmpty()) {
+ for (int i = 0; i < tPutFreeSlots.size(); i++) {
+ final int tPutSlot = tPutFreeSlots.get(i);
+ if (isAllowedToPutIntoSlot(toInventory, tPutSlot, putSide, tGrabStack, (byte) 64)) {
+ // allowed, now do moving
+ final int tMoved = moveStackFromSlotAToSlotB(
+ fromInventory,
+ toInventory,
+ grabSlot,
+ tPutSlot,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ (byte) (aMaxMoveAtOnce - tMovedItems),
+ aMinMoveAtOnce);
+ if (tMoved > 0) {
+ final ItemStack s = toInventory.getStackInSlot(tPutSlot);
+ if (s != null) {
+ // s might be null if tPutInventory is very special, e.g. infinity chest
+ // if s is null, we will not mark this slot as target candidate for anything
+ final int spare = Math
+ .min(s.getMaxStackSize(), toInventory.getInventoryStackLimit())
+ - s.stackSize;
+ if (spare > 0) {
+ final ItemId ssID = ItemId.createNoCopy(s);
+ // add back to spare space count
+ tPutItems.merge(ssID, spare, Integer::sum);
+ // add to partially filled slot list
+ tPutItemStacks.computeIfAbsent(ssID, k -> new ArrayList<>())
+ .add(s);
+ }
+ // this is no longer free
+ tPutFreeSlots.remove(i);
+ i--;
+ }
+ // else -> noop
+ // this is still a free slot. no need to do anything.
+ tTotalItemsMoved += tMoved;
+ tMovedItems += tMoved;
+ tStackSize -= tMoved;
+ if (tStackSize == 0) break;
+ }
+ }
+ }
+ }
+
+ if (tMovedItems > 0) {
+ // check if we have moved enough stacks
+ if (++tStacksMoved >= aMaxStackTransfer) return tTotalItemsMoved;
+ }
+ } while (tMovedItems > 0 && tStackSize > 0); // support inventories that store more than a stack in a
+ // slot
+ }
+
+ // check if source is a double chest, if yes, try move from the adjacent as well
+ if (aDoCheckChests && fromInventory instanceof TileEntityChest chest) {
+ final int tAmount = moveFromAdjacentChests(
+ chest,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer - tStacksMoved);
+ if (tAmount != 0) return tAmount + tTotalItemsMoved;
+ }
+
+ // check if target is a double chest, if yes, try move to the adjacent as well
+ if (aDoCheckChests && toObject instanceof TileEntityChest chest) {
+ final int tAmount = moveToAdjacentChests(
+ fromInventory,
+ chest,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer - tStacksMoved);
+ if (tAmount != 0) return tAmount + tTotalItemsMoved;
+ }
+
+ return tTotalItemsMoved;
+ }
+ // there should be a function to transfer more than 1 stack in a pipe
+ // however I do not see any ways to improve it. too much work for what it is worth
+ int tTotalItemsMoved = 0;
+ final int tGrabInventorySize = tGrabSlots.length;
+ for (int i = 0; i < tGrabInventorySize; i++) {
+ final int tMoved = moveStackIntoPipe(
+ fromInventory,
+ toObject,
+ tGrabSlots,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aDoCheckChests);
+ if (tMoved == 0) return tTotalItemsMoved;
+ else tTotalItemsMoved += tMoved;
+ }
+ return 0;
+ }
+
+ private static int moveToAdjacentChests(IInventory aTileEntity1, TileEntityChest aTargetChest,
+ ForgeDirection fromSide, ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter,
+ byte aMaxTargetStackSize, byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce,
+ int aMaxStackTransfer) {
+ if (aTargetChest.adjacentChestChecked) {
+ if (aTargetChest.adjacentChestXNeg != null) {
+ return moveMultipleItemStacks(
+ aTileEntity1,
+ aTargetChest.adjacentChestXNeg,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (aTargetChest.adjacentChestZNeg != null) {
+ return moveMultipleItemStacks(
+ aTileEntity1,
+ aTargetChest.adjacentChestZNeg,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (aTargetChest.adjacentChestXPos != null) {
+ return moveMultipleItemStacks(
+ aTileEntity1,
+ aTargetChest.adjacentChestXPos,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (aTargetChest.adjacentChestZPos != null) {
+ return moveMultipleItemStacks(
+ aTileEntity1,
+ aTargetChest.adjacentChestZPos,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ }
+ }
+ return 0;
+ }
+
+ private static int moveFromAdjacentChests(TileEntityChest fromTileEntityChest, Object toObject,
+ ForgeDirection fromSide, ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter,
+ byte aMaxTargetStackSize, byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce,
+ int aMaxStackTransfer) {
+ if (fromTileEntityChest.adjacentChestXNeg != null) {
+ return moveMultipleItemStacks(
+ fromTileEntityChest.adjacentChestXNeg,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (fromTileEntityChest.adjacentChestZNeg != null) {
+ return moveMultipleItemStacks(
+ fromTileEntityChest.adjacentChestZNeg,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (fromTileEntityChest.adjacentChestXPos != null) {
+ return moveMultipleItemStacks(
+ fromTileEntityChest.adjacentChestXPos,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ } else if (fromTileEntityChest.adjacentChestZPos != null) {
+ return moveMultipleItemStacks(
+ fromTileEntityChest.adjacentChestZPos,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aMaxStackTransfer,
+ false);
+ }
+ return 0;
+ }
+
+ /**
+ * Moves Stack from Inv-Side to Inv-Side.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveOneItemStack(Object fromObject, Object toObject, ForgeDirection fromSide,
+ ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize,
+ byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ if (fromObject instanceof IInventory inv) return moveOneItemStack(
+ inv,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ true);
+ return 0;
+ }
+
+ /**
+ * This is only because I needed an additional Parameter for the Double Chest Check.
+ */
+ private static byte moveOneItemStack(IInventory fromInventory, Object toObject, ForgeDirection fromSide,
+ ForgeDirection putSide, List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize,
+ byte aMinTargetStackSize, byte aMaxMoveAtOnce, byte aMinMoveAtOnce, boolean aDoCheckChests) {
+ if (fromInventory == null || aMaxTargetStackSize <= 0
+ || aMinTargetStackSize <= 0
+ || aMaxMoveAtOnce <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+
+ int[] tGrabSlots = null;
+ if (fromInventory instanceof ISidedInventory)
+ tGrabSlots = ((ISidedInventory) fromInventory).getAccessibleSlotsFromSide(fromSide.ordinal());
+ if (tGrabSlots == null) {
+ tGrabSlots = new int[fromInventory.getSizeInventory()];
+ for (int i = 0; i < tGrabSlots.length; i++) tGrabSlots[i] = i;
+ }
+
+ if (toObject instanceof IInventory inv) {
+ int[] tPutSlots = null;
+ if (toObject instanceof ISidedInventory sided)
+ tPutSlots = sided.getAccessibleSlotsFromSide(putSide.ordinal());
+
+ if (tPutSlots == null) {
+ tPutSlots = new int[inv.getSizeInventory()];
+ for (int i = 0; i < tPutSlots.length; i++) tPutSlots[i] = i;
+ }
+
+ for (final int tGrabSlot : tGrabSlots) {
+ byte tMovedItemCount = 0;
+ final ItemStack tGrabStack = fromInventory.getStackInSlot(tGrabSlot);
+ if (listContainsItem(aFilter, tGrabStack, true, aInvertFilter)
+ && (tGrabStack.stackSize >= aMinMoveAtOnce
+ && isAllowedToTakeFromSlot(fromInventory, tGrabSlot, fromSide, tGrabStack))) {
+ for (final int tPutSlot : tPutSlots) {
+ if (isAllowedToPutIntoSlot(inv, tPutSlot, putSide, tGrabStack, aMaxTargetStackSize)) {
+ tMovedItemCount += moveStackFromSlotAToSlotB(
+ fromInventory,
+ inv,
+ tGrabSlot,
+ tPutSlot,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ (byte) (aMaxMoveAtOnce - tMovedItemCount),
+ aMinMoveAtOnce);
+ if (tMovedItemCount >= aMaxMoveAtOnce || (tMovedItemCount > 0 && aMaxTargetStackSize < 64))
+ return tMovedItemCount;
+ }
+ }
+
+ }
+ if (tMovedItemCount > 0) return tMovedItemCount;
+ }
+
+ if (aDoCheckChests && fromInventory instanceof TileEntityChest fromChest
+ && (fromChest.adjacentChestChecked)) {
+ byte tAmount = 0;
+ if (fromChest.adjacentChestXNeg != null) {
+ tAmount = moveOneItemStack(
+ fromChest.adjacentChestXNeg,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (fromChest.adjacentChestZNeg != null) {
+ tAmount = moveOneItemStack(
+ fromChest.adjacentChestZNeg,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (fromChest.adjacentChestXPos != null) {
+ tAmount = moveOneItemStack(
+ fromChest.adjacentChestXPos,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (fromChest.adjacentChestZPos != null) {
+ tAmount = moveOneItemStack(
+ fromChest.adjacentChestZPos,
+ toObject,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ }
+ if (tAmount != 0) return tAmount;
+
+ }
+ if (aDoCheckChests && toObject instanceof TileEntityChest toChest && (toChest.adjacentChestChecked)) {
+ byte tAmount = 0;
+ if (toChest.adjacentChestXNeg != null) {
+ tAmount = moveOneItemStack(
+ fromInventory,
+ toChest.adjacentChestXNeg,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (toChest.adjacentChestZNeg != null) {
+ tAmount = moveOneItemStack(
+ fromInventory,
+ toChest.adjacentChestZNeg,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (toChest.adjacentChestXPos != null) {
+ tAmount = moveOneItemStack(
+ fromInventory,
+ toChest.adjacentChestXPos,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (toChest.adjacentChestZPos != null) {
+ tAmount = moveOneItemStack(
+ fromInventory,
+ toChest.adjacentChestZPos,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ }
+ if (tAmount != 0) return tAmount;
+
+ }
+ }
+
+ return moveStackIntoPipe(
+ fromInventory,
+ toObject,
+ tGrabSlots,
+ fromSide,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aDoCheckChests);
+ }
+
+ /**
+ * Moves Stack from Inv-Side to Inv-Slot.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveOneItemStackIntoSlot(Object fromTileEntity, Object toTileEntity, ForgeDirection fromSide,
+ int putSlot, List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize, byte aMinTargetStackSize,
+ byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ if (!(fromTileEntity instanceof IInventory fromInv) || aMaxTargetStackSize <= 0
+ || aMinTargetStackSize <= 0
+ || aMaxMoveAtOnce <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+
+ int[] tGrabSlots = null;
+ if (fromTileEntity instanceof ISidedInventory sided)
+ tGrabSlots = sided.getAccessibleSlotsFromSide(fromSide.ordinal());
+ if (tGrabSlots == null) {
+ tGrabSlots = new int[fromInv.getSizeInventory()];
+ for (int i = 0; i < tGrabSlots.length; i++) tGrabSlots[i] = i;
+ }
+
+ if (toTileEntity instanceof IInventory toInv) {
+ for (final int tGrabSlot : tGrabSlots) {
+ if (listContainsItem(aFilter, fromInv.getStackInSlot(tGrabSlot), true, aInvertFilter)) {
+ if (isAllowedToTakeFromSlot(fromInv, tGrabSlot, fromSide, fromInv.getStackInSlot(tGrabSlot))) {
+ if (isAllowedToPutIntoSlot(
+ toInv,
+ putSlot,
+ ForgeDirection.UNKNOWN,
+ fromInv.getStackInSlot(tGrabSlot),
+ aMaxTargetStackSize)) {
+ byte tMovedItemCount = moveStackFromSlotAToSlotB(
+ fromInv,
+ toInv,
+ tGrabSlot,
+ putSlot,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce);
+ if (tMovedItemCount > 0) return tMovedItemCount;
+ }
+ }
+ }
+ }
+ }
+
+ final ForgeDirection toSide = fromSide.getOpposite();
+ moveStackIntoPipe(
+ fromInv,
+ toTileEntity,
+ tGrabSlots,
+ fromSide,
+ ForgeDirection.UNKNOWN,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce);
+ return 0;
+ }
+
+ /**
+ * Moves Stack from Inv-Slot to Inv-Slot.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveFromSlotToSlot(IInventory fromInv, IInventory toInv, int aGrabFrom, int aPutTo,
+ List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize, byte aMinTargetStackSize,
+ byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ if (fromInv == null || toInv == null
+ || aGrabFrom < 0
+ || aPutTo < 0
+ || aMinTargetStackSize <= 0
+ || aMaxMoveAtOnce <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+ if (listContainsItem(aFilter, fromInv.getStackInSlot(aGrabFrom), true, aInvertFilter)) {
+ if (isAllowedToTakeFromSlot(
+ fromInv,
+ aGrabFrom,
+ ForgeDirection.UNKNOWN,
+ fromInv.getStackInSlot(aGrabFrom))) {
+ if (isAllowedToPutIntoSlot(
+ toInv,
+ aPutTo,
+ ForgeDirection.UNKNOWN,
+ fromInv.getStackInSlot(aGrabFrom),
+ aMaxTargetStackSize)) {
+ byte tMovedItemCount = moveStackFromSlotAToSlotB(
+ fromInv,
+ toInv,
+ aGrabFrom,
+ aPutTo,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce);
+ if (tMovedItemCount > 0) return tMovedItemCount;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Moves Stack from Inv-Side to Inv-Slot.
+ *
+ * @return the Amount of moved Items
+ */
+ public static byte moveFromSlotToSide(IInventory fromTile, Object toTile, int fromSlot, ForgeDirection putSide,
+ List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize, byte aMinTargetStackSize,
+ byte aMaxMoveAtOnce, byte aMinMoveAtOnce, boolean aDoCheckChests) {
+ if (fromTile == null || fromSlot < 0
+ || aMinTargetStackSize <= 0
+ || aMaxMoveAtOnce <= 0
+ || aMinTargetStackSize > aMaxTargetStackSize
+ || aMinMoveAtOnce > aMaxMoveAtOnce) return 0;
+
+ if (!listContainsItem(aFilter, fromTile.getStackInSlot(fromSlot), true, aInvertFilter)
+ || !isAllowedToTakeFromSlot(fromTile, fromSlot, ForgeDirection.UNKNOWN, fromTile.getStackInSlot(fromSlot)))
+ return 0;
+
+ if (toTile instanceof IInventory) {
+ int[] tPutSlots = null;
+ if (toTile instanceof ISidedInventory sided)
+ tPutSlots = sided.getAccessibleSlotsFromSide(putSide.ordinal());
+
+ if (tPutSlots == null) {
+ tPutSlots = new int[((IInventory) toTile).getSizeInventory()];
+ for (int i = 0; i < tPutSlots.length; i++) tPutSlots[i] = i;
+ }
+
+ byte tMovedItemCount = 0;
+ for (final int tPutSlot : tPutSlots) {
+ if (isAllowedToPutIntoSlot(
+ (IInventory) toTile,
+ tPutSlot,
+ putSide,
+ fromTile.getStackInSlot(fromSlot),
+ aMaxTargetStackSize)) {
+ tMovedItemCount += moveStackFromSlotAToSlotB(
+ fromTile,
+ (IInventory) toTile,
+ fromSlot,
+ tPutSlot,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ (byte) (aMaxMoveAtOnce - tMovedItemCount),
+ aMinMoveAtOnce);
+ if (tMovedItemCount >= aMaxMoveAtOnce) {
+ return tMovedItemCount;
+ }
+ }
+ }
+ if (tMovedItemCount > 0) return tMovedItemCount;
+
+ if (aDoCheckChests && toTile instanceof TileEntityChest tTileEntity2) {
+ if (tTileEntity2.adjacentChestChecked) {
+ if (tTileEntity2.adjacentChestXNeg != null) {
+ tMovedItemCount = moveFromSlotToSide(
+ fromTile,
+ tTileEntity2.adjacentChestXNeg,
+ fromSlot,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (tTileEntity2.adjacentChestZNeg != null) {
+ tMovedItemCount = moveFromSlotToSide(
+ fromTile,
+ tTileEntity2.adjacentChestZNeg,
+ fromSlot,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (tTileEntity2.adjacentChestXPos != null) {
+ tMovedItemCount = moveFromSlotToSide(
+ fromTile,
+ tTileEntity2.adjacentChestXPos,
+ fromSlot,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ } else if (tTileEntity2.adjacentChestZPos != null) {
+ tMovedItemCount = moveFromSlotToSide(
+ fromTile,
+ tTileEntity2.adjacentChestZPos,
+ fromSlot,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ false);
+ }
+ if (tMovedItemCount > 0) return tMovedItemCount;
+ }
+ }
+ }
+ return moveStackIntoPipe(
+ fromTile,
+ toTile,
+ new int[] { fromSlot },
+ ForgeDirection.UNKNOWN,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ aDoCheckChests);
+ }
+
+ public static byte moveFromSlotToSide(IInventory fromTile, Object toTile, int fromSlot, ForgeDirection putSide,
+ List<ItemStack> aFilter, boolean aInvertFilter, byte aMaxTargetStackSize, byte aMinTargetStackSize,
+ byte aMaxMoveAtOnce, byte aMinMoveAtOnce) {
+ return moveFromSlotToSide(
+ fromTile,
+ toTile,
+ fromSlot,
+ putSide,
+ aFilter,
+ aInvertFilter,
+ aMaxTargetStackSize,
+ aMinTargetStackSize,
+ aMaxMoveAtOnce,
+ aMinMoveAtOnce,
+ true);
+ }
+
+ /**
+ * Move up to maxAmount amount of fluid from source to dest, with optional filtering via allowMove. note that this
+ * filter cannot bypass filtering done by IFluidHandlers themselves.
+ *
+ * this overload will assume the fill side is the opposite of drainSide
+ *
+ * @param source tank to drain from. method become noop if this is null
+ * @param dest tank to fill to. method become noop if this is null
+ * @param drainSide side used during draining operation
+ * @param maxAmount max amount of fluid to transfer. method become noop if this is not a positive integer
+ * @param allowMove filter. can be null to signal all fluids are accepted
+ */
+ public static void moveFluid(IFluidHandler source, IFluidHandler dest, ForgeDirection drainSide, int maxAmount,
+ @Nullable Predicate<FluidStack> allowMove) {
+ moveFluid(source, dest, drainSide, drainSide.getOpposite(), maxAmount, allowMove);
+ }
+
+ /**
+ * Move up to maxAmount amount of fluid from source to dest, with optional filtering via allowMove. note that this
+ * filter cannot bypass filtering done by IFluidHandlers themselves.
+ *
+ * @param source tank to drain from. method become noop if this is null
+ * @param dest tank to fill to. method become noop if this is null
+ * @param drainSide side used during draining operation
+ * @param fillSide side used during filling operation
+ * @param maxAmount max amount of fluid to transfer. method become noop if this is not a positive integer
+ * @param allowMove filter. can be null to signal all fluids are accepted
+ */
+ public static void moveFluid(IFluidHandler source, IFluidHandler dest, ForgeDirection drainSide,
+ ForgeDirection fillSide, int maxAmount, @Nullable Predicate<FluidStack> allowMove) {
+ if (source == null || dest == null || maxAmount <= 0) return;
+ FluidStack liquid = source.drain(drainSide, maxAmount, false);
+ if (liquid == null) return;
+ liquid = liquid.copy();
+ liquid.amount = dest.fill(fillSide, liquid, false);
+ if (liquid.amount > 0 && (allowMove == null || allowMove.test(liquid))) {
+ dest.fill(fillSide, source.drain(drainSide, liquid.amount, true), true);
+ }
+ }
+
+ public static boolean listContainsItem(Collection<ItemStack> aList, ItemStack aStack, boolean aTIfListEmpty,
+ boolean aInvertFilter) {
+ if (aStack == null || aStack.stackSize < 1) return false;
+ if (aList == null) return aTIfListEmpty;
+ boolean tEmpty = true;
+ for (ItemStack tStack : aList) {
+ if (tStack != null) {
+ tEmpty = false;
+ if (areStacksEqual(aStack, tStack)) {
+ return !aInvertFilter;
+ }
+ }
+ }
+ return tEmpty ? aTIfListEmpty : aInvertFilter;
+ }
+
+ public static boolean areStacksOrToolsEqual(ItemStack aStack1, ItemStack aStack2) {
+ if (aStack1 != null && aStack2 != null && aStack1.getItem() == aStack2.getItem()) {
+ if (aStack1.getItem()
+ .isDamageable()) return true;
+ return ((aStack1.getTagCompound() == null) == (aStack2.getTagCompound() == null))
+ && (aStack1.getTagCompound() == null || aStack1.getTagCompound()
+ .equals(aStack2.getTagCompound()))
+ && (Items.feather.getDamage(aStack1) == Items.feather.getDamage(aStack2)
+ || Items.feather.getDamage(aStack1) == W
+ || Items.feather.getDamage(aStack2) == W);
+ }
+ return false;
+ }
+
+ public static boolean areFluidsEqual(FluidStack aFluid1, FluidStack aFluid2) {
+ return areFluidsEqual(aFluid1, aFluid2, false);
+ }
+
+ public static boolean areFluidsEqual(FluidStack aFluid1, FluidStack aFluid2, boolean aIgnoreNBT) {
+ return aFluid1 != null && aFluid2 != null
+ && aFluid1.getFluid() == aFluid2.getFluid()
+ && (aIgnoreNBT || ((aFluid1.tag == null) == (aFluid2.tag == null))
+ && (aFluid1.tag == null || aFluid1.tag.equals(aFluid2.tag)));
+ }
+
+ public static boolean areStacksEqual(ItemStack aStack1, ItemStack aStack2) {
+ return areStacksEqual(aStack1, aStack2, false);
+ }
+
+ public static boolean areStacksEqual(ItemStack aStack1, ItemStack aStack2, boolean aIgnoreNBT) {
+ return aStack1 != null && aStack2 != null
+ && aStack1.getItem() == aStack2.getItem()
+ && (aIgnoreNBT || (((aStack1.getTagCompound() == null) == (aStack2.getTagCompound() == null))
+ && (aStack1.getTagCompound() == null || aStack1.getTagCompound()
+ .equals(aStack2.getTagCompound()))))
+ && (Items.feather.getDamage(aStack1) == Items.feather.getDamage(aStack2)
+ || Items.feather.getDamage(aStack1) == W
+ || Items.feather.getDamage(aStack2) == W);
+ }
+
+ public static boolean areStacksEqualOrNull(ItemStack stack1, ItemStack stack2) {
+ return (stack1 == null && stack2 == null) || GT_Utility.areStacksEqual(stack1, stack2);
+ }
+
+ /**
+ * Treat both null list, or both null item stack at same list position as equal.
+ * <p>
+ * Since ItemStack doesn't override equals and hashCode, you cannot just use Objects.equals
+ */
+ public static boolean areStackListsEqual(List<ItemStack> lhs, List<ItemStack> rhs, boolean ignoreStackSize,
+ boolean ignoreNBT) {
+ if (lhs == null) return rhs == null;
+ if (rhs == null) return false;
+ if (lhs.size() != rhs.size()) return false;
+ for (Iterator<ItemStack> it1 = lhs.iterator(), it2 = rhs.iterator(); it1.hasNext() && it2.hasNext();) {
+ if (!areStacksEqualExtended(it1.next(), it2.next(), ignoreStackSize, ignoreNBT)) return false;
+ }
+ return true;
+ }
+
+ private static boolean areStacksEqualExtended(ItemStack lhs, ItemStack rhs, boolean ignoreStackSize,
+ boolean ignoreNBT) {
+ if (lhs == null) return rhs == null;
+ if (rhs == null) return false;
+ return lhs.getItem() == rhs.getItem()
+ && (ignoreNBT || Objects.equals(lhs.stackTagCompound, rhs.stackTagCompound))
+ && (ignoreStackSize || lhs.stackSize == rhs.stackSize);
+ }
+
+ public static boolean areUnificationsEqual(ItemStack aStack1, ItemStack aStack2) {
+ return areUnificationsEqual(aStack1, aStack2, false);
+ }
+
+ public static boolean areUnificationsEqual(ItemStack aStack1, ItemStack aStack2, boolean aIgnoreNBT) {
+ return areStacksEqual(
+ GT_OreDictUnificator.get_nocopy(aStack1),
+ GT_OreDictUnificator.get_nocopy(aStack2),
+ aIgnoreNBT);
+ }
+
+ public static String getFluidName(Fluid aFluid, boolean aLocalized) {
+ if (aFluid == null) return E;
+ String rName = aLocalized ? aFluid.getLocalizedName(new FluidStack(aFluid, 0)) : aFluid.getUnlocalizedName();
+ if (rName.contains("fluid.") || rName.contains("tile.")) return capitalizeString(
+ rName.replaceAll("fluid.", E)
+ .replaceAll("tile.", E));
+ return rName;
+ }
+
+ public static String getFluidName(FluidStack aFluid, boolean aLocalized) {
+ if (aFluid == null) return E;
+ return getFluidName(aFluid.getFluid(), aLocalized);
+ }
+
+ public static void reInit() {
+ sFilledContainerToData.clear();
+ sEmptyContainerToFluidToData.clear();
+ sFluidToContainers.clear();
+ for (FluidContainerData tData : sFluidContainerList) {
+ String fluidName = tData.fluid.getFluid()
+ .getName();
+ sFilledContainerToData.put(new GT_ItemStack(tData.filledContainer), tData);
+ Map<String, FluidContainerData> tFluidToContainer = sEmptyContainerToFluidToData
+ .get(new GT_ItemStack(tData.emptyContainer));
+ List<ItemStack> tContainers = sFluidToContainers.get(fluidName);
+ if (tFluidToContainer == null) {
+ sEmptyContainerToFluidToData
+ .put(new GT_ItemStack(tData.emptyContainer), tFluidToContainer = new /* Concurrent */ HashMap<>());
+ }
+ tFluidToContainer.put(fluidName, tData);
+ if (tContainers == null) {
+ tContainers = new ArrayList<>();
+ tContainers.add(tData.filledContainer);
+ sFluidToContainers.put(fluidName, tContainers);
+ } else tContainers.add(tData.filledContainer);
+ }
+ }
+
+ public static void addFluidContainerData(FluidContainerData aData) {
+ String fluidName = aData.fluid.getFluid()
+ .getName();
+ sFluidContainerList.add(aData);
+ sFilledContainerToData.put(new GT_ItemStack(aData.filledContainer), aData);
+ Map<String, FluidContainerData> tFluidToContainer = sEmptyContainerToFluidToData
+ .get(new GT_ItemStack(aData.emptyContainer));
+ List<ItemStack> tContainers = sFluidToContainers.get(fluidName);
+ if (tFluidToContainer == null) {
+ sEmptyContainerToFluidToData
+ .put(new GT_ItemStack(aData.emptyContainer), tFluidToContainer = new /* Concurrent */ HashMap<>());
+ }
+ tFluidToContainer.put(fluidName, aData);
+ if (tContainers == null) {
+ tContainers = new ArrayList<>();
+ tContainers.add(aData.filledContainer);
+ sFluidToContainers.put(fluidName, tContainers);
+ } else tContainers.add(aData.filledContainer);
+ }
+
+ public static List<ItemStack> getContainersFromFluid(FluidStack tFluidStack) {
+ if (tFluidStack != null) {
+ List<ItemStack> tContainers = sFluidToContainers.get(
+ tFluidStack.getFluid()
+ .getName());
+ if (tContainers == null) return new ArrayList<>();
+ return tContainers;
+ }
+ return new ArrayList<>();
+ }
+
+ public static ItemStack fillFluidContainer(FluidStack aFluid, ItemStack aStack, boolean aRemoveFluidDirectly,
+ boolean aCheckIFluidContainerItems) {
+ if (isStackInvalid(aStack) || aFluid == null) return null;
+ if (GT_ModHandler.isWater(aFluid) && ItemList.Bottle_Empty.isStackEqual(aStack)) {
+ if (aFluid.amount >= 1000) {
+ return new ItemStack(Items.potionitem, 1, 0);
+ }
+ return null;
+ }
+ if (aCheckIFluidContainerItems && aStack.getItem() instanceof IFluidContainerItem
+ && ((IFluidContainerItem) aStack.getItem()).getFluid(aStack) == null
+ && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) <= aFluid.amount) {
+ if (aRemoveFluidDirectly) aFluid.amount -= ((IFluidContainerItem) aStack.getItem())
+ .fill(aStack = copyAmount(1, aStack), aFluid, true);
+ else((IFluidContainerItem) aStack.getItem()).fill(aStack = copyAmount(1, aStack), aFluid, true);
+ return aStack;
+ }
+ Map<String, FluidContainerData> tFluidToContainer = sEmptyContainerToFluidToData.get(new GT_ItemStack(aStack));
+ if (tFluidToContainer == null) return null;
+ FluidContainerData tData = tFluidToContainer.get(
+ aFluid.getFluid()
+ .getName());
+ if (tData == null || tData.fluid.amount > aFluid.amount) return null;
+ if (aRemoveFluidDirectly) aFluid.amount -= tData.fluid.amount;
+ return copyAmount(1, tData.filledContainer);
+ }
+
+ public static int calculateRecipeEU(Materials aMaterial, int defaultRecipeEUPerTick) {
+ return aMaterial.getProcessingMaterialTierEU() == 0 ? defaultRecipeEUPerTick
+ : aMaterial.getProcessingMaterialTierEU();
+ }
+
+ public static ItemStack getFluidDisplayStack(Fluid aFluid) {
+ return aFluid == null ? null : getFluidDisplayStack(new FluidStack(aFluid, 0), false);
+ }
+
+ public static ItemStack getFluidDisplayStack(FluidStack aFluid, boolean aUseStackSize) {
+ return getFluidDisplayStack(aFluid, aUseStackSize, false);
+ }
+
+ public static ItemStack getFluidDisplayStack(FluidStack aFluid, boolean aUseStackSize, boolean aHideStackSize) {
+ if (aFluid == null || aFluid.getFluid() == null) return null;
+ int tmp = 0;
+ try {
+ tmp = aFluid.getFluid()
+ .getID();
+ } catch (Exception e) {
+ System.err.println(e);
+ }
+ ItemStack rStack = ItemList.Display_Fluid.getWithDamage(1, tmp);
+ NBTTagCompound tNBT = new NBTTagCompound();
+ tNBT.setLong("mFluidDisplayAmount", aUseStackSize ? aFluid.amount : 0);
+ tNBT.setLong(
+ "mFluidDisplayHeat",
+ aFluid.getFluid()
+ .getTemperature(aFluid));
+ tNBT.setBoolean(
+ "mFluidState",
+ aFluid.getFluid()
+ .isGaseous(aFluid));
+ tNBT.setBoolean("mHideStackSize", aHideStackSize);
+ try {
+ tNBT.setString("mFluidMaterialName", FLUID_MAP.get(aFluid.getFluid()).mName);
+ } catch (Exception ignored) {}
+ rStack.setTagCompound(tNBT);
+ return rStack;
+ }
+
+ public static FluidStack getFluidFromDisplayStack(ItemStack aDisplayStack) {
+ if (!isStackValid(aDisplayStack) || aDisplayStack.getItem() != ItemList.Display_Fluid.getItem()
+ || !aDisplayStack.hasTagCompound()) return null;
+ Fluid tFluid = FluidRegistry.getFluid(
+ ItemList.Display_Fluid.getItem()
+ .getDamage(aDisplayStack));
+ return new FluidStack(
+ tFluid,
+ (int) aDisplayStack.getTagCompound()
+ .getLong("mFluidDisplayAmount"));
+ }
+
+ public static boolean containsFluid(ItemStack aStack, FluidStack aFluid, boolean aCheckIFluidContainerItems) {
+ if (isStackInvalid(aStack) || aFluid == null) return false;
+ if (aCheckIFluidContainerItems && aStack.getItem() instanceof IFluidContainerItem
+ && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) > 0)
+ return aFluid
+ .isFluidEqual(((IFluidContainerItem) aStack.getItem()).getFluid(aStack = copyAmount(1, aStack)));
+ FluidContainerData tData = sFilledContainerToData.get(new GT_ItemStack(aStack));
+ return tData != null && tData.fluid.isFluidEqual(aFluid);
+ }
+
+ public static FluidStack getFluidForFilledItem(ItemStack aStack, boolean aCheckIFluidContainerItems) {
+ if (isStackInvalid(aStack)) return null;
+ if (aCheckIFluidContainerItems && aStack.getItem() instanceof IFluidContainerItem
+ && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) > 0)
+ return ((IFluidContainerItem) aStack.getItem()).drain(copyAmount(1, aStack), Integer.MAX_VALUE, true);
+ FluidContainerData tData = sFilledContainerToData.get(new GT_ItemStack(aStack));
+ return tData == null ? null : tData.fluid.copy();
+ }
+
+ /**
+ * Get empty fluid container from filled one.
+ */
+ public static ItemStack getContainerForFilledItem(ItemStack aStack, boolean aCheckIFluidContainerItems) {
+ if (isStackInvalid(aStack)) return null;
+ FluidContainerData tData = sFilledContainerToData.get(new GT_ItemStack(aStack));
+ if (tData != null) return copyAmount(1, tData.emptyContainer);
+ if (aCheckIFluidContainerItems && aStack.getItem() instanceof IFluidContainerItem
+ && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) > 0) {
+ ((IFluidContainerItem) aStack.getItem()).drain(aStack = copyAmount(1, aStack), Integer.MAX_VALUE, true);
+ return aStack;
+ }
+ return null;
+ }
+
+ /**
+ * This is NOT meant for fluid manipulation! It's for getting item container, which is generally used for
+ * crafting recipes. While it also works for many of the fluid containers, some don't.
+ * <p>
+ * Use {@link #getContainerForFilledItem} for getting empty fluid container.
+ */
+ public static ItemStack getContainerItem(ItemStack aStack, boolean aCheckIFluidContainerItems) {
+ if (isStackInvalid(aStack)) return null;
+ if (aStack.getItem()
+ .hasContainerItem(aStack))
+ return aStack.getItem()
+ .getContainerItem(aStack);
+ /*
+ * These are all special Cases, in which it is intended to have only GT Blocks outputting those Container Items
+ */
+ if (ItemList.Cell_Empty.isStackEqual(aStack, false, true)) return null;
+ if (aStack.getItem() == Items.potionitem || aStack.getItem() == Items.experience_bottle
+ || ItemList.TF_Vial_FieryBlood.isStackEqual(aStack)
+ || ItemList.TF_Vial_FieryTears.isStackEqual(aStack)) return ItemList.Bottle_Empty.get(1);
+
+ if (aCheckIFluidContainerItems && aStack.getItem() instanceof IFluidContainerItem
+ && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) > 0) {
+ ItemStack tStack = copyAmount(1, aStack);
+ ((IFluidContainerItem) aStack.getItem()).drain(tStack, Integer.MAX_VALUE, true);
+ if (!areStacksEqual(aStack, tStack)) return tStack;
+ return null;
+ }
+
+ int tCapsuleCount = GT_ModHandler.getCapsuleCellContainerCount(aStack);
+ if (tCapsuleCount > 0) return ItemList.Cell_Empty.get(tCapsuleCount);
+
+ if (ItemList.IC2_ForgeHammer.isStackEqual(aStack) || ItemList.IC2_WireCutter.isStackEqual(aStack))
+ return copyMetaData(Items.feather.getDamage(aStack) + 1, aStack);
+ return null;
+ }
+
+ public static FluidStack getFluidFromContainerOrFluidDisplay(ItemStack stack) {
+ FluidStack fluidStack = GT_Utility.getFluidForFilledItem(stack, true);
+ if (fluidStack == null) {
+ fluidStack = GT_Utility.getFluidFromDisplayStack(stack);
+ }
+ return fluidStack;
+ }
+
+ public static synchronized boolean removeIC2BottleRecipe(ItemStack aContainer, ItemStack aInput,
+ Map<ic2.api.recipe.ICannerBottleRecipeManager.Input, RecipeOutput> aRecipeList, ItemStack aOutput) {
+ if ((isStackInvalid(aInput) && isStackInvalid(aOutput) && isStackInvalid(aContainer)) || aRecipeList == null)
+ return false;
+ boolean rReturn = false;
+ Iterator<Map.Entry<ic2.api.recipe.ICannerBottleRecipeManager.Input, RecipeOutput>> tIterator = aRecipeList
+ .entrySet()
+ .iterator();
+ aOutput = GT_OreDictUnificator.get(aOutput);
+ while (tIterator.hasNext()) {
+ Map.Entry<ic2.api.recipe.ICannerBottleRecipeManager.Input, RecipeOutput> tEntry = tIterator.next();
+ if (aInput == null || tEntry.getKey()
+ .matches(aContainer, aInput)) {
+ List<ItemStack> tList = tEntry.getValue().items;
+ if (tList != null) for (ItemStack tOutput : tList)
+ if (aOutput == null || areStacksEqual(GT_OreDictUnificator.get(tOutput), aOutput)) {
+ tIterator.remove();
+ rReturn = true;
+ break;
+ }
+ }
+ }
+ return rReturn;
+ }
+
+ public static synchronized boolean removeSimpleIC2MachineRecipe(ItemStack aInput,
+ Map<IRecipeInput, RecipeOutput> aRecipeList, ItemStack aOutput) {
+ if ((isStackInvalid(aInput) && isStackInvalid(aOutput)) || aRecipeList == null) return false;
+ boolean rReturn = false;
+ Iterator<Map.Entry<IRecipeInput, RecipeOutput>> tIterator = aRecipeList.entrySet()
+ .iterator();
+ aOutput = GT_OreDictUnificator.get(aOutput);
+ while (tIterator.hasNext()) {
+ Map.Entry<IRecipeInput, RecipeOutput> tEntry = tIterator.next();
+ if (aInput == null || tEntry.getKey()
+ .matches(aInput)) {
+ List<ItemStack> tList = tEntry.getValue().items;
+ if (tList != null) for (ItemStack tOutput : tList)
+ if (aOutput == null || areStacksEqual(GT_OreDictUnificator.get(tOutput), aOutput)) {
+ tIterator.remove();
+ rReturn = true;
+ break;
+ }
+ }
+ }
+ return rReturn;
+ }
+
+ public static synchronized void bulkRemoveSimpleIC2MachineRecipe(Map<ItemStack, ItemStack> toRemove,
+ Map<IRecipeInput, RecipeOutput> aRecipeList) {
+ if (aRecipeList == null || aRecipeList.isEmpty()) return;
+ toRemove.entrySet()
+ .removeIf(aEntry -> (isStackInvalid(aEntry.getKey()) && isStackInvalid(aEntry.getValue())));
+ final Map<ItemStack, ItemStack> finalToRemove = Maps
+ .transformValues(toRemove, GT_OreDictUnificator::get_nocopy);
+
+ aRecipeList.entrySet()
+ .removeIf(
+ tEntry -> finalToRemove.entrySet()
+ .stream()
+ .anyMatch(aEntry -> {
+ final ItemStack aInput = aEntry.getKey(), aOutput = aEntry.getValue();
+ final List<ItemStack> tList = tEntry.getValue().items;
+
+ if (tList == null) return false;
+ if (aInput != null && !tEntry.getKey()
+ .matches(aInput)) return false;
+
+ return tList.stream()
+ .anyMatch(
+ tOutput -> (aOutput == null
+ || areStacksEqual(GT_OreDictUnificator.get(tOutput), aOutput)));
+ }));
+ }
+
+ public static boolean addSimpleIC2MachineRecipe(ItemStack aInput, Map<IRecipeInput, RecipeOutput> aRecipeList,
+ NBTTagCompound aNBT, Object... aOutput) {
+ if (isStackInvalid(aInput) || aOutput.length == 0 || aRecipeList == null) return false;
+ ItemData tOreName = GT_OreDictUnificator.getAssociation(aInput);
+ for (Object o : aOutput) {
+ if (o == null) {
+ GT_FML_LOGGER.info("EmptyIC2Output!" + aInput.getUnlocalizedName());
+ return false;
+ }
+ }
+ ItemStack[] tStack = GT_OreDictUnificator.getStackArray(true, aOutput);
+ if (tStack.length > 0 && areStacksEqual(aInput, tStack[0])) return false;
+ if (tOreName != null) {
+ if (tOreName.toString()
+ .equals("dustAsh")
+ && tStack[0].getUnlocalizedName()
+ .equals("tile.volcanicAsh"))
+ return false;
+ aRecipeList
+ .put(new RecipeInputOreDict(tOreName.toString(), aInput.stackSize), new RecipeOutput(aNBT, tStack));
+ } else {
+ aRecipeList
+ .put(new RecipeInputItemStack(copyOrNull(aInput), aInput.stackSize), new RecipeOutput(aNBT, tStack));
+ }
+ return true;
+ }
+
+ public static ItemStack getWrittenBook(String aMapping, ItemStack aStackToPutNBT) {
+ if (isStringInvalid(aMapping)) return null;
+ ItemStack rStack = GregTech_API.sBookList.get(aMapping);
+ if (rStack == null) return aStackToPutNBT;
+ if (aStackToPutNBT != null) {
+ aStackToPutNBT.setTagCompound(rStack.getTagCompound());
+ return aStackToPutNBT;
+ }
+ return copyAmount(1, rStack);
+ }
+
+ public static ItemStack getWrittenBook(String aMapping, String aTitle, String aAuthor, String... aPages) {
+ if (isStringInvalid(aMapping)) return null;
+ ItemStack rStack = GregTech_API.sBookList.get(aMapping);
+ if (rStack != null) return copyAmount(1, rStack);
+ if (isStringInvalid(aTitle) || isStringInvalid(aAuthor) || aPages.length == 0) return null;
+ sBookCount++;
+ rStack = new ItemStack(Items.written_book, 1);
+ NBTTagCompound tNBT = new NBTTagCompound();
+ tNBT.setString("title", GT_LanguageManager.addStringLocalization("Book." + aTitle + ".Name", aTitle));
+ tNBT.setString("author", aAuthor);
+ NBTTagList tNBTList = new NBTTagList();
+ for (byte i = 0; i < aPages.length; i++) {
+ aPages[i] = GT_LanguageManager
+ .addStringLocalization("Book." + aTitle + ".Page" + ((i < 10) ? "0" + i : i), aPages[i]);
+ if (i < 48) {
+ if (aPages[i].length() < 256) tNBTList.appendTag(new NBTTagString(aPages[i]));
+ else GT_Log.err.println("WARNING: String for written Book too long! -> " + aPages[i]);
+ } else {
+ GT_Log.err.println("WARNING: Too much Pages for written Book! -> " + aTitle);
+ break;
+ }
+ }
+ tNBTList.appendTag(
+ new NBTTagString(
+ "Credits to " + aAuthor
+ + " for writing this Book. This was Book Nr. "
+ + sBookCount
+ + " at its creation. Gotta get 'em all!"));
+ tNBT.setTag("pages", tNBTList);
+ rStack.setTagCompound(tNBT);
+ GT_Log.out.println(
+ "GT_Mod: Added Book to Book List - Mapping: '" + aMapping
+ + "' - Name: '"
+ + aTitle
+ + "' - Author: '"
+ + aAuthor
+ + "'");
+ GregTech_API.sBookList.put(aMapping, rStack);
+ return copyOrNull(rStack);
+ }
+
+ public static boolean doSoundAtClient(String aSoundName, int aTimeUntilNextSound, float aSoundStrength) {
+ if (aSoundName == null) return false;
+ return doSoundAtClient(aSoundName, aTimeUntilNextSound, aSoundStrength, GT.getThePlayer());
+ }
+
+ public static boolean doSoundAtClient(SoundResource sound, int aTimeUntilNextSound, float aSoundStrength) {
+ return doSoundAtClient(sound.resourceLocation, aTimeUntilNextSound, aSoundStrength, GT.getThePlayer());
+ }
+
+ public static boolean doSoundAtClient(ResourceLocation aSoundResourceLocation, int aTimeUntilNextSound,
+ float aSoundStrength) {
+ return doSoundAtClient(aSoundResourceLocation, aTimeUntilNextSound, aSoundStrength, GT.getThePlayer());
+ }
+
+ public static boolean doSoundAtClient(String aSoundName, int aTimeUntilNextSound, float aSoundStrength,
+ Entity aEntity) {
+ if (aEntity == null || aSoundName == null) return false;
+ return doSoundAtClient(
+ aSoundName,
+ aTimeUntilNextSound,
+ aSoundStrength,
+ aEntity.posX,
+ aEntity.posY,
+ aEntity.posZ);
+ }
+
+ public static boolean doSoundAtClient(ResourceLocation aSoundResourceLocation, int aTimeUntilNextSound,
+ float aSoundStrength, Entity aEntity) {
+ if (aEntity == null) return false;
+ return doSoundAtClient(
+ aSoundResourceLocation.toString(),
+ aTimeUntilNextSound,
+ aSoundStrength,
+ aEntity.posX,
+ aEntity.posY,
+ aEntity.posZ);
+ }
+
+ public static boolean doSoundAtClient(ResourceLocation aSoundResourceLocation, int aTimeUntilNextSound,
+ float aSoundStrength, double aX, double aY, double aZ) {
+ return doSoundAtClient(aSoundResourceLocation, aTimeUntilNextSound, aSoundStrength, 1.01818028F, aX, aY, aZ);
+ }
+
+ /**
+ * @inheritDoc
+ * @deprecated Use {@link #doSoundAtClient(ResourceLocation, int, float, double, double, double)}
+ */
+ @Deprecated
+ public static boolean doSoundAtClient(String aSoundName, int aTimeUntilNextSound, float aSoundStrength, double aX,
+ double aY, double aZ) {
+ if (aSoundName == null) return false;
+ return doSoundAtClient(
+ new ResourceLocation(aSoundName),
+ aTimeUntilNextSound,
+ aSoundStrength,
+ 1.01818028F,
+ aX,
+ aY,
+ aZ);
+ }
+
+ public static boolean doSoundAtClient(SoundResource aSound, int aTimeUntilNextSound, float aSoundStrength,
+ double aX, double aY, double aZ) {
+ return doSoundAtClient(aSound.resourceLocation, aTimeUntilNextSound, aSoundStrength, aX, aY, aZ);
+ }
+
+ public static boolean doSoundAtClient(SoundResource aSound, int aTimeUntilNextSound, float aSoundStrength,
+ float aSoundModulation, double aX, double aY, double aZ) {
+ return doSoundAtClient(
+ aSound.resourceLocation,
+ aTimeUntilNextSound,
+ aSoundStrength,
+ aSoundModulation,
+ aX,
+ aY,
+ aZ);
+ }
+
+ public static boolean doSoundAtClient(ResourceLocation aSoundResourceLocation, int aTimeUntilNextSound,
+ float aSoundStrength, float aSoundModulation, double aX, double aY, double aZ) {
+ if (!FMLCommonHandler.instance()
+ .getEffectiveSide()
+ .isClient() || GT.getThePlayer() == null || !GT.getThePlayer().worldObj.isRemote) return false;
+ if (GregTech_API.sMultiThreadedSounds) new Thread(
+ new GT_Runnable_Sound(
+ GT.getThePlayer().worldObj,
+ aX,
+ aY,
+ aZ,
+ aTimeUntilNextSound,
+ aSoundResourceLocation,
+ aSoundStrength,
+ aSoundModulation),
+ "Sound Effect").start();
+ else new GT_Runnable_Sound(
+ GT.getThePlayer().worldObj,
+ aX,
+ aY,
+ aZ,
+ aTimeUntilNextSound,
+ aSoundResourceLocation,
+ aSoundStrength,
+ aSoundModulation).run();
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ * @Deprecated Use {@link #doSoundAtClient(ResourceLocation, int, float, float, double, double, double)}
+ */
+ @Deprecated
+ public static boolean doSoundAtClient(String aSoundName, int aTimeUntilNextSound, float aSoundStrength,
+ float aSoundModulation, double aX, double aY, double aZ) {
+ if (isStringInvalid(aSoundName)) return false;
+ return doSoundAtClient(
+ new ResourceLocation(aSoundName),
+ aTimeUntilNextSound,
+ aSoundStrength,
+ aSoundModulation,
+ aX,
+ aY,
+ aZ);
+ }
+
+ public static boolean sendSoundToPlayers(World aWorld, String aSoundName, float aSoundStrength,
+ float aSoundModulation, int aX, int aY, int aZ) {
+ if (isStringInvalid(aSoundName) || aWorld == null || aWorld.isRemote) return false;
+ NW.sendPacketToAllPlayersInRange(
+ aWorld,
+ new GT_Packet_Sound(aSoundName, aSoundStrength, aSoundModulation, aX, (short) aY, aZ),
+ aX,
+ aZ);
+ return true;
+ }
+
+ public static boolean sendSoundToPlayers(World aWorld, SoundResource sound, float aSoundStrength,
+ float aSoundModulation, int aX, int aY, int aZ) {
+ if (aWorld == null || aWorld.isRemote) return false;
+ NW.sendPacketToAllPlayersInRange(
+ aWorld,
+ new GT_Packet_Sound(
+ sound.resourceLocation.toString(),
+ aSoundStrength,
+ aSoundModulation,
+ aX,
+ (short) aY,
+ aZ),
+ aX,
+ aZ);
+ return true;
+ }
+
+ public static int stackToInt(ItemStack aStack) {
+ if (isStackInvalid(aStack)) return 0;
+ return itemToInt(aStack.getItem(), Items.feather.getDamage(aStack));
+ }
+
+ public static int itemToInt(Item aItem, int aMeta) {
+ return Item.getIdFromItem(aItem) | (aMeta << 16);
+ }
+
+ public static int stackToWildcard(ItemStack aStack) {
+ if (isStackInvalid(aStack)) return 0;
+ return Item.getIdFromItem(aStack.getItem()) | (W << 16);
+ }
+
+ public static ItemStack intToStack(int aStack) {
+ int tID = aStack & (~0 >>> 16), tMeta = aStack >>> 16;
+ Item tItem = Item.getItemById(tID);
+ if (tItem != null) return new ItemStack(tItem, 1, tMeta);
+ return null;
+ }
+
+ public static Integer[] stacksToIntegerArray(ItemStack... aStacks) {
+ Integer[] rArray = new Integer[aStacks.length];
+ for (int i = 0; i < rArray.length; i++) {
+ rArray[i] = stackToInt(aStacks[i]);
+ }
+ return rArray;
+ }
+
+ public static int[] stacksToIntArray(ItemStack... aStacks) {
+ int[] rArray = new int[aStacks.length];
+ for (int i = 0; i < rArray.length; i++) {
+ rArray[i] = stackToInt(aStacks[i]);
+ }
+ return rArray;
+ }
+
+ public static boolean arrayContains(Object aObject, Object... aObjects) {
+ return listContains(aObject, Arrays.asList(aObjects));
+ }
+
+ public static boolean listContains(Object aObject, Collection<?> aObjects) {
+ if (aObjects == null) return false;
+ return aObjects.contains(aObject);
+ }
+
+ @SafeVarargs
+ public static <T> boolean arrayContainsNonNull(T... aArray) {
+ if (aArray != null) for (Object tObject : aArray) if (tObject != null) return true;
+ return false;
+ }
+
+ /**
+ * Note: use {@link ArrayExt#withoutNulls(Object[], IntFunction)} if you want an array as a result.
+ */
+ @SafeVarargs
+ public static <T> ArrayList<T> getArrayListWithoutNulls(T... aArray) {
+ if (aArray == null) return new ArrayList<>();
+ ArrayList<T> rList = new ArrayList<>(Arrays.asList(aArray));
+ for (int i = 0; i < rList.size(); i++) if (rList.get(i) == null) rList.remove(i--);
+ return rList;
+ }
+
+ /**
+ * Note: use {@link ArrayExt#withoutTrailingNulls(Object[], IntFunction)} if you want an array as a result.
+ */
+ @SafeVarargs
+ public static <T> ArrayList<T> getArrayListWithoutTrailingNulls(T... aArray) {
+ if (aArray == null) return new ArrayList<>();
+ ArrayList<T> rList = new ArrayList<>(Arrays.asList(aArray));
+ for (int i = rList.size() - 1; i >= 0 && rList.get(i) == null;) rList.remove(i--);
+ return rList;
+ }
+
+ @Deprecated // why do you use Objects?
+ public static Block getBlock(Object aBlock) {
+ return (Block) aBlock;
+ }
+
+ public static Block getBlockFromStack(ItemStack itemStack) {
+ if (isStackInvalid(itemStack)) return Blocks.air;
+ return getBlockFromItem(itemStack.getItem());
+ }
+
+ public static Block getBlockFromItem(Item item) {
+ return Block.getBlockFromItem(item);
+ }
+
+ @Deprecated // why do you use Objects? And if you want to check your block to be not null, check it directly!
+ public static boolean isBlockValid(Object aBlock) {
+ return (aBlock instanceof Block);
+ }
+
+ @Deprecated // why do you use Objects? And if you want to check your block to be null, check it directly!
+ public static boolean isBlockInvalid(Object aBlock) {
+ return !(aBlock instanceof Block);
+ }
+
+ public static boolean isStringValid(Object aString) {
+ return aString != null && !aString.toString()
+ .isEmpty();
+ }
+
+ public static boolean isStringInvalid(Object aString) {
+ return aString == null || aString.toString()
+ .isEmpty();
+ }
+
+ @Deprecated
+ public static boolean isStackValid(Object aStack) {
+ return (aStack instanceof ItemStack stack) && isStackValid(stack);
+ }
+
+ public static boolean isStackValid(ItemStack aStack) {
+ return (aStack != null) && aStack.getItem() != null && aStack.stackSize >= 0;
+ }
+
+ @Deprecated
+ public static boolean isStackInvalid(Object aStack) {
+ return !(aStack instanceof ItemStack stack) || isStackInvalid(stack);
+ }
+
+ public static boolean isStackInvalid(ItemStack aStack) {
+ return aStack == null || aStack.getItem() == null || aStack.stackSize < 0;
+ }
+
+ public static boolean isDebugItem(ItemStack aStack) {
+ return /* ItemList.Armor_Cheat.isStackEqual(aStack, T, T) || */ areStacksEqual(
+ GT_ModHandler.getIC2Item("debug", 1),
+ aStack,
+ true);
+ }
+
+ public static ItemStack updateItemStack(ItemStack aStack) {
+ if (isStackValid(aStack) && aStack.getItem() instanceof GT_Generic_Item)
+ ((GT_Generic_Item) aStack.getItem()).isItemStackUsable(aStack);
+ return aStack;
+ }
+
+ public static boolean isOpaqueBlock(World aWorld, int aX, int aY, int aZ) {
+ return aWorld.getBlock(aX, aY, aZ)
+ .isOpaqueCube();
+ }
+
+ public static boolean isBlockAir(World aWorld, int aX, int aY, int aZ) {
+ return aWorld.getBlock(aX, aY, aZ)
+ .isAir(aWorld, aX, aY, aZ);
+ }
+
+ public static boolean hasBlockHitBox(World aWorld, int aX, int aY, int aZ) {
+ return aWorld.getBlock(aX, aY, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY, aZ) != null;
+ }
+
+ public static void setCoordsOnFire(World aWorld, int aX, int aY, int aZ, boolean aReplaceCenter) {
+ if (aReplaceCenter) if (aWorld.getBlock(aX, aY, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY, aZ) == null) aWorld.setBlock(aX, aY, aZ, Blocks.fire);
+ if (aWorld.getBlock(aX + 1, aY, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX + 1, aY, aZ) == null)
+ aWorld.setBlock(aX + 1, aY, aZ, Blocks.fire);
+ if (aWorld.getBlock(aX - 1, aY, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX - 1, aY, aZ) == null)
+ aWorld.setBlock(aX - 1, aY, aZ, Blocks.fire);
+ if (aWorld.getBlock(aX, aY + 1, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY + 1, aZ) == null)
+ aWorld.setBlock(aX, aY + 1, aZ, Blocks.fire);
+ if (aWorld.getBlock(aX, aY - 1, aZ)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY - 1, aZ) == null)
+ aWorld.setBlock(aX, aY - 1, aZ, Blocks.fire);
+ if (aWorld.getBlock(aX, aY, aZ + 1)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY, aZ + 1) == null)
+ aWorld.setBlock(aX, aY, aZ + 1, Blocks.fire);
+ if (aWorld.getBlock(aX, aY, aZ - 1)
+ .getCollisionBoundingBoxFromPool(aWorld, aX, aY, aZ - 1) == null)
+ aWorld.setBlock(aX, aY, aZ - 1, Blocks.fire);
+ }
+
+ public static ItemStack getProjectile(SubTag aProjectileType, IInventory aInventory) {
+ if (aInventory != null) for (int i = 0, j = aInventory.getSizeInventory(); i < j; i++) {
+ ItemStack rStack = aInventory.getStackInSlot(i);
+ if (isStackValid(rStack) && rStack.getItem() instanceof IProjectileItem
+ && ((IProjectileItem) rStack.getItem()).hasProjectile(aProjectileType, rStack))
+ return updateItemStack(rStack);
+ }
+ return null;
+ }
+
+ public static void removeNullStacksFromInventory(IInventory aInventory) {
+ if (aInventory != null) for (int i = 0, j = aInventory.getSizeInventory(); i < j; i++) {
+ ItemStack tStack = aInventory.getStackInSlot(i);
+ if (tStack != null && (tStack.stackSize == 0 || tStack.getItem() == null))
+ aInventory.setInventorySlotContents(i, null);
+ }
+ }
+
+ /**
+ * Initializes new empty texture page for casings page 0 is old CASING_BLOCKS
+ * <p>
+ * Then casings should be registered like this: for (byte i = MIN_USED_META; i < MAX_USED_META; i = (byte) (i + 1))
+ * { Textures.BlockIcons.casingTexturePages[PAGE][i+START_INDEX] = new GT_CopiedBlockTexture(this, 6, i); }
+ *
+ * @param page 0 to 127
+ * @return true if it made empty page, false if one already existed...
+ */
+ public static boolean addTexturePage(byte page) {
+ if (Textures.BlockIcons.casingTexturePages[page] == null) {
+ Textures.BlockIcons.casingTexturePages[page] = new ITexture[128];
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return texture id from page and index, for use when determining hatches, but can also be precomputed from:
+ * (page<<7)+index
+ *
+ * @param page 0 to 127 page
+ * @param index 0 to 127 texture index
+ * @return casing texture 0 to 16383
+ */
+ public static int getTextureId(byte page, byte index) {
+ if (page >= 0 && index >= 0) {
+ return (page << 7) + index;
+ }
+ throw new RuntimeException("Index out of range: [" + page + "][" + index + "]");
+ }
+
+ /**
+ * Return texture id from page and index, for use when determining hatches, but can also be precomputed from:
+ * (page<<7)+index
+ *
+ * @param page 0 to 127 page
+ * @param startIndex 0 to 127 start texture index
+ * @param blockMeta meta of the block
+ * @return casing texture 0 to 16383
+ */
+ public static int getTextureId(byte page, byte startIndex, byte blockMeta) {
+ if (page >= 0 && startIndex >= 0 && blockMeta >= 0 && (startIndex + blockMeta) <= 127) {
+ return (page << 7) + (startIndex + blockMeta);
+ }
+ throw new RuntimeException(
+ "Index out of range: [" + page
+ + "]["
+ + startIndex
+ + "+"
+ + blockMeta
+ + "="
+ + (startIndex + blockMeta)
+ + "]");
+ }
+
+ /**
+ * Return texture id from item stack, unoptimized but readable?
+ *
+ * @return casing texture 0 to 16383
+ */
+ @Deprecated
+ public static int getTextureId(ItemStack stack) {
+ return getTextureId(Block.getBlockFromItem(stack.getItem()), (byte) stack.getItemDamage());
+ }
+
+ /**
+ * Return texture id from item stack, unoptimized but readable?
+ *
+ * @return casing texture 0 to 16383
+ */
+ public static int getTextureId(Block blockFromBlock, byte metaFromBlock) {
+ for (int page = 0; page < Textures.BlockIcons.casingTexturePages.length; page++) {
+ ITexture[] casingTexturePage = Textures.BlockIcons.casingTexturePages[page];
+ if (casingTexturePage != null) {
+ for (int index = 0; index < casingTexturePage.length; index++) {
+ ITexture iTexture = casingTexturePage[index];
+ if (iTexture instanceof IBlockContainer) {
+ Block block = ((IBlockContainer) iTexture).getBlock();
+ byte meta = ((IBlockContainer) iTexture).getMeta();
+ if (meta == metaFromBlock && blockFromBlock == block) {
+ return (page << 7) + index;
+ }
+ }
+ }
+ }
+ }
+ throw new RuntimeException(
+ "Probably missing mapping or different texture class used for: " + blockFromBlock.getUnlocalizedName()
+ + " meta:"
+ + metaFromBlock);
+ }
+
+ /**
+ * Converts a Number to a String
+ */
+ public static String parseNumberToString(int aNumber) {
+ boolean temp = true, negative = false;
+
+ if (aNumber < 0) {
+ aNumber *= -1;
+ negative = true;
+ }
+
+ StringBuilder tStringB = new StringBuilder();
+ for (int i = 1000000000; i > 0; i /= 10) {
+ int tDigit = (aNumber / i) % 10;
+ if (temp && tDigit != 0) temp = false;
+ if (!temp) {
+ tStringB.append(tDigit);
+ if (i != 1) for (int j = i; j > 0; j /= 1000) if (j == 1) tStringB.append(",");
+ }
+ }
+
+ String tString = tStringB.toString();
+
+ if (tString.equals(E)) tString = "0";
+
+ return negative ? "-" + tString : tString;
+ }
+
+ public static NBTTagCompound getNBTContainingBoolean(NBTTagCompound aNBT, Object aTag, boolean aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setBoolean(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingByte(NBTTagCompound aNBT, Object aTag, byte aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setByte(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingShort(NBTTagCompound aNBT, Object aTag, short aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setShort(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingInteger(NBTTagCompound aNBT, Object aTag, int aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setInteger(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingFloat(NBTTagCompound aNBT, Object aTag, float aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setFloat(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingDouble(NBTTagCompound aNBT, Object aTag, double aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ aNBT.setDouble(aTag.toString(), aValue);
+ return aNBT;
+ }
+
+ public static NBTTagCompound getNBTContainingString(NBTTagCompound aNBT, Object aTag, Object aValue) {
+ if (aNBT == null) aNBT = new NBTTagCompound();
+ if (aValue == null) return aNBT;
+ aNBT.setString(aTag.toString(), aValue.toString());
+ return aNBT;
+ }
+
+ public static boolean isWearingFullFrostHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sFrostHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isWearingFullHeatHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sHeatHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean isWearingFullBioHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sBioHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isWearingFullRadioHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sRadioHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isWearingFullElectroHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sElectroHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isWearingFullGasHazmat(EntityLivingBase aEntity) {
+ for (byte i = 1; i < 5; i++) {
+ ItemStack tStack = aEntity.getEquipmentInSlot(i);
+
+ if (!isStackInList(tStack, GregTech_API.sGasHazmatList) && !hasHazmatEnchant(tStack)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean hasHazmatEnchant(ItemStack aStack) {
+ if (aStack == null) return false;
+ Map<Integer, Integer> tEnchantments = EnchantmentHelper.getEnchantments(aStack);
+ Integer tLevel = tEnchantments.get(Enchantment_Hazmat.INSTANCE.effectId);
+
+ return tLevel != null && tLevel >= 1;
+ }
+
+ public static float getHeatDamageFromItem(ItemStack aStack) {
+ ItemData tData = GT_OreDictUnificator.getItemData(aStack);
+ return tData == null ? 0
+ : (tData.mPrefix == null ? 0 : tData.mPrefix.mHeatDamage)
+ + (tData.hasValidMaterialData() ? tData.mMaterial.mMaterial.mHeatDamage : 0);
+ }
+
+ public static int getRadioactivityLevel(ItemStack aStack) {
+ ItemData tData = GT_OreDictUnificator.getItemData(aStack);
+ if (tData != null && tData.hasValidMaterialData()) {
+ if (tData.mMaterial.mMaterial.mEnchantmentArmors instanceof Enchantment_Radioactivity)
+ return tData.mMaterial.mMaterial.mEnchantmentArmorsLevel;
+ if (tData.mMaterial.mMaterial.mEnchantmentTools instanceof Enchantment_Radioactivity)
+ return tData.mMaterial.mMaterial.mEnchantmentToolsLevel;
+ }
+ return EnchantmentHelper.getEnchantmentLevel(Enchantment_Radioactivity.INSTANCE.effectId, aStack);
+ }
+
+ public static boolean isImmuneToBreathingGasses(EntityLivingBase aEntity) {
+ return isWearingFullGasHazmat(aEntity);
+ }
+
+ public static boolean applyHeatDamage(EntityLivingBase entity, float damage) {
+ return applyHeatDamage(entity, damage, GT_DamageSources.getHeatDamage());
+ }
+
+ public static boolean applyHeatDamageFromItem(EntityLivingBase entity, float damage, ItemStack item) {
+ return applyHeatDamage(entity, damage, new DamageSourceHotItem(item));
+ }
+
+ private static boolean applyHeatDamage(EntityLivingBase aEntity, float aDamage, DamageSource source) {
+ if (aDamage > 0 && aEntity != null && !isWearingFullHeatHazmat(aEntity)) {
+ return aEntity.attackEntityFrom(source, aDamage);
+ }
+ return false;
+ }
+
+ public static boolean applyFrostDamage(EntityLivingBase aEntity, float aDamage) {
+ if (aDamage > 0 && aEntity != null && !isWearingFullFrostHazmat(aEntity)) {
+ return aEntity.attackEntityFrom(GT_DamageSources.getFrostDamage(), aDamage);
+ }
+ return false;
+ }
+
+ public static boolean applyElectricityDamage(EntityLivingBase aEntity, long aVoltage, long aAmperage) {
+ long aDamage = getTier(aVoltage) * aAmperage * 4;
+ if (aDamage > 0 && aEntity != null && !isWearingFullElectroHazmat(aEntity)) {
+ return aEntity.attackEntityFrom(GT_DamageSources.getElectricDamage(), aDamage);
+ }
+ return false;
+ }
+
+ public static boolean applyRadioactivity(EntityLivingBase aEntity, int aLevel, int aAmountOfItems) {
+ if (aLevel > 0 && aEntity != null
+ && aEntity.getCreatureAttribute() != EnumCreatureAttribute.UNDEAD
+ && aEntity.getCreatureAttribute() != EnumCreatureAttribute.ARTHROPOD
+ && !isWearingFullRadioHazmat(aEntity)) {
+ PotionEffect tEffect = null;
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ Potion.moveSlowdown.id,
+ aLevel * 140 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.moveSlowdown)) == null ? 0
+ : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ Potion.digSlowdown.id,
+ aLevel * 150 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.digSlowdown)) == null ? 0
+ : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ Potion.confusion.id,
+ aLevel * 130 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.confusion)) == null ? 0
+ : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ Potion.weakness.id,
+ aLevel * 150 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.weakness)) == null ? 0
+ : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ Potion.hunger.id,
+ aLevel * 130 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.hunger)) == null ? 0 : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ aEntity.addPotionEffect(
+ new PotionEffect(
+ 24 /* IC2 Radiation */,
+ aLevel * 180 * aAmountOfItems + Math.max(
+ 0,
+ ((tEffect = aEntity.getActivePotionEffect(Potion.potionTypes[24])) == null ? 0
+ : tEffect.getDuration())),
+ Math.max(0, (5 * aLevel) / 7)));
+ return true;
+ }
+ return false;
+ }
+
+ @Deprecated
+ public static ItemStack setStack(Object aSetStack, Object aToStack) {
+ if (aSetStack instanceof ItemStack setStack && aToStack instanceof ItemStack toStack)
+ return setStack(setStack, toStack);
+ return null;
+ }
+
+ public static ItemStack setStack(ItemStack aSetStack, ItemStack aToStack) {
+ if (isStackInvalid(aSetStack) || isStackInvalid(aToStack)) return null;
+ aSetStack.func_150996_a(aToStack.getItem());
+ aSetStack.stackSize = aToStack.stackSize;
+ Items.feather.setDamage(aSetStack, Items.feather.getDamage(aToStack));
+ aSetStack.setTagCompound(aToStack.getTagCompound());
+ return aSetStack;
+ }
+
+ public static FluidStack[] copyFluidArray(FluidStack... aStacks) {
+ if (aStacks == null) return null;
+ FluidStack[] rStacks = new FluidStack[aStacks.length];
+ for (int i = 0; i < aStacks.length; i++) if (aStacks[i] != null) rStacks[i] = aStacks[i].copy();
+ return rStacks;
+ }
+
+ public static ItemStack[] copyItemArray(ItemStack... aStacks) {
+ if (aStacks == null) return null;
+ ItemStack[] rStacks = new ItemStack[aStacks.length];
+ for (int i = 0; i < aStacks.length; i++) rStacks[i] = copy(aStacks[i]);
+ return rStacks;
+ }
+
+ /**
+ * @deprecated use {@link #copy(ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copy(Object... aStacks) {
+ for (Object tStack : aStacks) if (isStackValid(tStack)) return ((ItemStack) tStack).copy();
+ return null;
+ }
+
+ public static ItemStack copy(ItemStack aStack) {
+ if (isStackValid(aStack)) return aStack.copy();
+ return null;
+ }
+
+ @Deprecated
+ private static ItemStack firstStackOrNull(Object... aStacks) {
+ for (Object tStack : aStacks) if (tStack instanceof ItemStack stack) return stack;
+ return null;
+ }
+
+ @Nullable
+ public static ItemStack copyOrNull(@Nullable ItemStack stack) {
+ if (isStackValid(stack)) return stack.copy();
+ return null;
+ }
+
+ public static FluidStack copyAmount(int aAmount, FluidStack aStack) {
+ if (aStack == null) return null;
+ FluidStack rStack = aStack.copy();
+ rStack.amount = aAmount;
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #copyAmount(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyAmount(long aAmount, Object... aStacks) {
+ return copyAmount(aAmount, firstStackOrNull(aStacks));
+ }
+
+ /**
+ * @deprecated use {@link #copyAmount(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyAmount(long aAmount, ItemStack aStack) {
+ return copyAmount((int) aAmount, aStack);
+ }
+
+ public static ItemStack copyAmount(int aAmount, ItemStack aStack) {
+ ItemStack rStack = copy(aStack);
+ if (isStackInvalid(rStack)) return null;
+ if (aAmount > 64) aAmount = 64;
+ else if (aAmount == -1) aAmount = 111;
+ else if (aAmount < 0) aAmount = 0;
+ rStack.stackSize = (byte) aAmount;
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #multiplyStack(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack multiplyStack(long aMultiplier, Object... aStacks) {
+ return multiplyStack((int) aMultiplier, firstStackOrNull(aStacks));
+ }
+
+ public static ItemStack multiplyStack(int aMultiplier, ItemStack aStack) {
+ ItemStack rStack = copy(aStack);
+ if (isStackInvalid(rStack)) return null;
+ int tAmount = rStack.stackSize * aMultiplier;
+ if (tAmount > 64) tAmount = 64;
+ else if (tAmount == -1) tAmount = 111;
+ else if (tAmount < 0) tAmount = 0;
+ rStack.stackSize = (byte) tAmount;
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #copyAmountUnsafe(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyAmountUnsafe(long aAmount, Object... aStacks) {
+ return copyAmountUnsafe((int) aAmount, firstStackOrNull(aStacks));
+ }
+
+ /**
+ * Unlike {@link #copyAmount(int, ItemStack)}, this method does not restrict stack size by 64.
+ */
+ public static ItemStack copyAmountUnsafe(int aAmount, ItemStack aStack) {
+ ItemStack rStack = copy(aStack);
+ if (isStackInvalid(rStack)) return null;
+ else if (aAmount < 0) aAmount = 0;
+ rStack.stackSize = (int) aAmount;
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #copyMetaData(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyMetaData(long aMetaData, Object... aStacks) {
+ return copyMetaData((int) aMetaData, firstStackOrNull(aStacks));
+ }
+
+ public static ItemStack copyMetaData(int aMetaData, ItemStack aStack) {
+ ItemStack rStack = copy(aStack);
+ if (isStackInvalid(rStack)) return null;
+ Items.feather.setDamage(rStack, aMetaData);
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #copyAmountAndMetaData(int, int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyAmountAndMetaData(long aAmount, long aMetaData, Object... aStacks) {
+ return copyAmountAndMetaData(aAmount, aMetaData, firstStackOrNull(aStacks));
+ }
+
+ /**
+ * @deprecated use {@link #copyAmountAndMetaData(int, int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack copyAmountAndMetaData(long aAmount, long aMetaData, ItemStack aStack) {
+ return copyAmountAndMetaData((int) aAmount, (int) aMetaData, aStack);
+ }
+
+ public static ItemStack copyAmountAndMetaData(int aAmount, int aMetaData, ItemStack aStack) {
+ ItemStack rStack = copyAmount(aAmount, aStack);
+ if (isStackInvalid(rStack)) return null;
+ Items.feather.setDamage(rStack, aMetaData);
+ return rStack;
+ }
+
+ /**
+ * @deprecated use {@link #mul(int, ItemStack)} instead
+ */
+ @Deprecated
+ public static ItemStack mul(long aMultiplier, Object... aStacks) {
+ return mul((int) aMultiplier, firstStackOrNull(aStacks));
+ }
+
+ /**
+ * returns a copy of an ItemStack with its Stacksize being multiplied by aMultiplier
+ */
+ public static ItemStack mul(int aMultiplier, ItemStack aStack) {
+ ItemStack rStack = copy(aStack);
+ if (rStack == null) return null;
+ rStack.stackSize *= aMultiplier;
+ return rStack;
+ }
+
+ /**
+ * Loads an ItemStack properly.
+ */
+ public static ItemStack loadItem(NBTTagCompound aNBT, String aTagName) {
+ return loadItem(aNBT.getCompoundTag(aTagName));
+ }
+
+ public static FluidStack loadFluid(NBTTagCompound aNBT, String aTagName) {
+ return loadFluid(aNBT.getCompoundTag(aTagName));
+ }
+
+ /**
+ * Loads an ItemStack properly.
+ */
+ public static ItemStack loadItem(NBTTagCompound aNBT) {
+ if (aNBT == null) return null;
+ ItemStack tRawStack = ItemStack.loadItemStackFromNBT(aNBT);
+ int tRealStackSize = 0;
+ if (tRawStack != null && aNBT.hasKey("Count", Constants.NBT.TAG_INT)) {
+ tRealStackSize = aNBT.getInteger("Count");
+ tRawStack.stackSize = tRealStackSize;
+ } else if (tRawStack != null) {
+ tRealStackSize = tRawStack.stackSize;
+ }
+ ItemStack tRet = GT_OreDictUnificator.get(true, tRawStack);
+ if (tRet != null) tRet.stackSize = tRealStackSize;
+ return tRet;
+ }
+
+ public static void saveItem(NBTTagCompound aParentTag, String aTagName, ItemStack aStack) {
+ if (aStack != null) aParentTag.setTag(aTagName, saveItem(aStack));
+ }
+
+ public static NBTTagCompound saveItem(ItemStack aStack) {
+ if (aStack == null) return new NBTTagCompound();
+ NBTTagCompound t = new NBTTagCompound();
+ aStack.writeToNBT(t);
+ if (aStack.stackSize > Byte.MAX_VALUE) t.setInteger("Count", aStack.stackSize);
+ return t;
+ }
+
+ /**
+ * Loads an FluidStack properly.
+ */
+ public static FluidStack loadFluid(NBTTagCompound aNBT) {
+ if (aNBT == null) return null;
+ return FluidStack.loadFluidStackFromNBT(aNBT);
+ }
+
+ public static <E> E selectItemInList(int aIndex, E aReplacement, List<E> aList) {
+ if (aList == null || aList.isEmpty()) return aReplacement;
+ if (aList.size() <= aIndex) return aList.get(aList.size() - 1);
+ if (aIndex < 0) return aList.get(0);
+ return aList.get(aIndex);
+ }
+
+ @SafeVarargs
+ public static <E> E selectItemInList(int aIndex, E aReplacement, E... aList) {
+ if (aList == null || aList.length == 0) return aReplacement;
+ if (aList.length <= aIndex) return aList[aList.length - 1];
+ if (aIndex < 0) return aList[0];
+ return aList[aIndex];
+ }
+
+ public static boolean isStackInStackSet(ItemStack aStack, Set<ItemStack> aSet) {
+ if (aStack == null) return false;
+ if (aSet.contains(aStack)) return true;
+
+ return aSet.contains(GT_ItemStack.internalCopyStack(aStack, true));
+ }
+
+ public static boolean isStackInList(ItemStack aStack, Collection<GT_ItemStack> aList) {
+ if (aStack == null) {
+ return false;
+ }
+ return isStackInList(new GT_ItemStack(aStack), aList);
+ }
+
+ public static boolean isStackInList(ItemStack aStack, Set<GT_ItemStack2> aList) {
+ if (aStack == null) {
+ return false;
+ }
+ return isStackInList(new GT_ItemStack2(aStack), aList);
+ }
+
+ public static boolean isStackInList(GT_ItemStack aStack, Collection<GT_ItemStack> aList) {
+ return aStack != null
+ && (aList.contains(aStack) || aList.contains(new GT_ItemStack(aStack.mItem, aStack.mStackSize, W)));
+ }
+
+ public static boolean isStackInList(GT_ItemStack2 aStack, Set<GT_ItemStack2> aList) {
+ return aStack != null
+ && (aList.contains(aStack) || aList.contains(new GT_ItemStack2(aStack.mItem, aStack.mStackSize, W)));
+ }
+
+ /**
+ * re-maps all Keys of a Map after the Keys were weakened.
+ */
+ public static <X, Y> Map<X, Y> reMap(Map<X, Y> aMap) {
+ Map<X, Y> tMap = null;
+ // We try to clone the Map first (most Maps are Cloneable) in order to retain as much state of the Map as
+ // possible when rehashing. For example, "Custom" HashMaps from fastutil may have a custom hash function which
+ // would not be used to rehash if we just create a new HashMap.
+ if (aMap instanceof Cloneable) {
+ try {
+ tMap = (Map<X, Y>) aMap.getClass()
+ .getMethod("clone")
+ .invoke(aMap);
+ } catch (Throwable e) {
+ GT_Log.err.println("Failed to clone Map of type " + aMap.getClass());
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ if (tMap == null) {
+ tMap = new HashMap<>(aMap);
+ }
+
+ aMap.clear();
+ aMap.putAll(tMap);
+ return aMap;
+ }
+
+ /**
+ * re-maps all Keys of a Map after the Keys were weakened.
+ */
+ public static <X, Y> SetMultimap<X, Y> reMap(SetMultimap<X, Y> aMap) {
+ @SuppressWarnings("unchecked")
+ Map.Entry<X, Y>[] entries = aMap.entries()
+ .toArray(new Map.Entry[0]);
+ aMap.clear();
+ for (Map.Entry<X, Y> entry : entries) {
+ aMap.put(entry.getKey(), entry.getValue());
+ }
+ return aMap;
+ }
+
+ public static <X, Y extends Comparable<Y>> LinkedHashMap<X, Y> sortMapByValuesAcending(Map<X, Y> map) {
+ return map.entrySet()
+ .stream()
+ .sorted(Map.Entry.comparingByValue())
+ .collect(CollectorUtils.entriesToMap(LinkedHashMap::new));
+ }
+
+ /**
+ * Why the fuck do neither Java nor Guava have a Function to do this?
+ */
+ public static <X, Y extends Comparable<Y>> LinkedHashMap<X, Y> sortMapByValuesDescending(Map<X, Y> aMap) {
+ List<Map.Entry<X, Y>> tEntrySet = new LinkedList<>(aMap.entrySet());
+ tEntrySet.sort((aValue1, aValue2) -> {
+ return aValue2.getValue()
+ .compareTo(aValue1.getValue()); // FB: RV - RV_NEGATING_RESULT_OF_COMPARETO
+ });
+ LinkedHashMap<X, Y> rMap = new LinkedHashMap<>();
+ for (Map.Entry<X, Y> tEntry : tEntrySet) rMap.put(tEntry.getKey(), tEntry.getValue());
+ return rMap;
+ }
+
+ /**
+ * Translates a Material Amount into an Amount of Fluid in Fluid Material Units.
+ */
+ public static long translateMaterialToFluidAmount(long aMaterialAmount, boolean aRoundUp) {
+ return translateMaterialToAmount(aMaterialAmount, L, aRoundUp);
+ }
+
+ /**
+ * Translates a Material Amount into an Amount of Fluid. Second Parameter for things like Bucket Amounts (1000) and
+ * similar
+ */
+ public static long translateMaterialToAmount(long aMaterialAmount, long aAmountPerUnit, boolean aRoundUp) {
+ return Math.max(
+ 0,
+ ((aMaterialAmount * aAmountPerUnit) / M)
+ + (aRoundUp && (aMaterialAmount * aAmountPerUnit) % M > 0 ? 1 : 0));
+ }
+
+ /**
+ * This checks if the Dimension is really a Dimension and not another Planet or something. Used for my Teleporter.
+ */
+ public static boolean isRealDimension(int aDimensionID) {
+ if (aDimensionID <= 1 && aDimensionID >= -1 && !GregTech_API.sDimensionalList.contains(aDimensionID))
+ return true;
+ return !GregTech_API.sDimensionalList.contains(aDimensionID)
+ && DimensionManager.isDimensionRegistered(aDimensionID);
+ }
+
+ public static boolean moveEntityToDimensionAtCoords(Entity entity, int aDimension, double aX, double aY,
+ double aZ) {
+ // Credit goes to BrandonCore Author :!:
+
+ if (entity == null || entity.worldObj.isRemote) return false;
+ if (entity.ridingEntity != null) entity.mountEntity(null);
+ if (entity.riddenByEntity != null) entity.riddenByEntity.mountEntity(null);
+
+ World startWorld = entity.worldObj;
+ WorldServer destinationWorld = FMLCommonHandler.instance()
+ .getMinecraftServerInstance()
+ .worldServerForDimension(aDimension);
+
+ if (destinationWorld == null) {
+ return false;
+ }
+
+ boolean interDimensional = startWorld.provider.dimensionId != destinationWorld.provider.dimensionId;
+ if (!interDimensional) return false;
+ startWorld.updateEntityWithOptionalForce(entity, false); // added
+
+ if (entity instanceof EntityPlayerMP player) {
+ player.closeScreen(); // added
+ player.dimension = aDimension;
+ player.playerNetServerHandler.sendPacket(
+ new S07PacketRespawn(
+ player.dimension,
+ player.worldObj.difficultySetting,
+ destinationWorld.getWorldInfo()
+ .getTerrainType(),
+ player.theItemInWorldManager.getGameType()));
+ ((WorldServer) startWorld).getPlayerManager()
+ .removePlayer(player);
+
+ startWorld.playerEntities.remove(player);
+ startWorld.updateAllPlayersSleepingFlag();
+ int i = entity.chunkCoordX;
+ int j = entity.chunkCoordZ;
+ if ((entity.addedToChunk) && (startWorld.getChunkProvider()
+ .chunkExists(i, j))) {
+ startWorld.getChunkFromChunkCoords(i, j)
+ .removeEntity(entity);
+ startWorld.getChunkFromChunkCoords(i, j).isModified = true;
+ }
+ startWorld.loadedEntityList.remove(entity);
+ startWorld.onEntityRemoved(entity);
+ }
+
+ entity.setLocationAndAngles(aX, aY, aZ, entity.rotationYaw, entity.rotationPitch);
+
+ destinationWorld.theChunkProviderServer.loadChunk((int) aX >> 4, (int) aZ >> 4);
+
+ destinationWorld.theProfiler.startSection("placing");
+ if (!(entity instanceof EntityPlayer)) {
+ NBTTagCompound entityNBT = new NBTTagCompound();
+ entity.isDead = false;
+ entityNBT.setString("id", EntityList.getEntityString(entity));
+ entity.writeToNBT(entityNBT);
+ entity.isDead = true;
+ entity = EntityList.createEntityFromNBT(entityNBT, destinationWorld);
+ if (entity == null) {
+ return false;
+ }
+ entity.dimension = destinationWorld.provider.dimensionId;
+ }
+ destinationWorld.spawnEntityInWorld(entity);
+ entity.setWorld(destinationWorld);
+ entity.setLocationAndAngles(aX, aY, aZ, entity.rotationYaw, entity.rotationPitch);
+
+ destinationWorld.updateEntityWithOptionalForce(entity, false);
+ entity.setLocationAndAngles(aX, aY, aZ, entity.rotationYaw, entity.rotationPitch);
+
+ if ((entity instanceof EntityPlayerMP player)) {
+ player.mcServer.getConfigurationManager()
+ .func_72375_a(player, destinationWorld);
+ player.playerNetServerHandler.setPlayerLocation(aX, aY, aZ, player.rotationYaw, player.rotationPitch);
+ }
+
+ destinationWorld.updateEntityWithOptionalForce(entity, false);
+
+ if (entity instanceof EntityPlayerMP player) {
+ player.theItemInWorldManager.setWorld(destinationWorld);
+ player.mcServer.getConfigurationManager()
+ .updateTimeAndWeatherForPlayer(player, destinationWorld);
+ player.mcServer.getConfigurationManager()
+ .syncPlayerInventory(player);
+
+ for (PotionEffect potionEffect : player.getActivePotionEffects()) {
+ player.playerNetServerHandler.sendPacket(new S1DPacketEntityEffect(player.getEntityId(), potionEffect));
+ }
+
+ player.playerNetServerHandler.sendPacket(
+ new S1FPacketSetExperience(player.experience, player.experienceTotal, player.experienceLevel));
+ FMLCommonHandler.instance()
+ .firePlayerChangedDimensionEvent(
+ player,
+ startWorld.provider.dimensionId,
+ destinationWorld.provider.dimensionId);
+ }
+ entity.setLocationAndAngles(aX, aY, aZ, entity.rotationYaw, entity.rotationPitch);
+
+ destinationWorld.theProfiler.endSection();
+ entity.fallDistance = 0;
+ return true;
+ }
+
+ public static int getScaleCoordinates(double aValue, int aScale) {
+ return (int) Math.floor(aValue / aScale);
+ }
+
+ public static int getCoordinateScan(ArrayList<String> aList, EntityPlayer aPlayer, World aWorld, int aScanLevel,
+ int aX, int aY, int aZ, ForgeDirection side, float aClickX, float aClickY, float aClickZ) {
+ if (aList == null) return 0;
+
+ ArrayList<String> tList = new ArrayList<>();
+ int rEUAmount = 0;
+
+ TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+
+ final Block tBlock = aWorld.getBlock(aX, aY, aZ);
+
+ addBaseInfo(aPlayer, aWorld, aX, aY, aZ, tList, tTileEntity, tBlock);
+
+ if (tTileEntity != null) {
+ rEUAmount += addFluidHandlerInfo(side, tList, tTileEntity);
+
+ try {
+ if (tTileEntity instanceof ic2.api.reactor.IReactorChamber chamber) {
+ rEUAmount += 500;
+ // Redirect the rest of the scans to the reactor itself
+ tTileEntity = (TileEntity) chamber.getReactor();
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ rEUAmount += addReactorInfo(tList, tTileEntity);
+ rEUAmount += addAlignmentInfo(tList, tTileEntity);
+ rEUAmount += addIC2WrenchableInfo(aPlayer, tList, tTileEntity);
+ rEUAmount += addIC2EnergyConductorInfo(tList, tTileEntity);
+ rEUAmount += addIC2EnergyStorageInfo(tList, tTileEntity);
+ rEUAmount += addUpgradableMachineInfo(tList, tTileEntity);
+ rEUAmount += addMachineProgressInfo(tList, tTileEntity);
+ rEUAmount += addCoverableInfo(side, tList, tTileEntity);
+ addEnergyContainerInfo(tList, tTileEntity);
+ addOwnerInfo(tList, tTileEntity);
+ addDeviceInfo(tList, tTileEntity);
+
+ rEUAmount += addIC2CropInfo(tList, tTileEntity);
+
+ rEUAmount += addForestryLeavesInfo(tList, tTileEntity);
+ }
+
+ final Chunk currentChunk = aWorld.getChunkFromBlockCoords(aX, aZ);
+ addUndergroundFluidInfo(aPlayer, tList, currentChunk);
+ addPollutionInfo(tList, currentChunk);
+
+ rEUAmount += addDebuggableBlockInfo(aPlayer, aX, aY, aZ, tList, tBlock);
+
+ final BlockScanningEvent tEvent = new BlockScanningEvent(
+ aWorld,
+ aPlayer,
+ aX,
+ aY,
+ aZ,
+ side,
+ aScanLevel,
+ tBlock,
+ tTileEntity,
+ tList,
+ aClickX,
+ aClickY,
+ aClickZ);
+ tEvent.mEUCost = rEUAmount;
+ MinecraftForge.EVENT_BUS.post(tEvent);
+ if (!tEvent.isCanceled()) aList.addAll(tList);
+ return tEvent.mEUCost;
+ }
+
+ private static void addBaseInfo(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, ArrayList<String> tList,
+ TileEntity tTileEntity, Block tBlock) {
+ tList.add(
+ "----- X: " + EnumChatFormatting.AQUA
+ + formatNumbers(aX)
+ + EnumChatFormatting.RESET
+ + " Y: "
+ + EnumChatFormatting.AQUA
+ + formatNumbers(aY)
+ + EnumChatFormatting.RESET
+ + " Z: "
+ + EnumChatFormatting.AQUA
+ + formatNumbers(aZ)
+ + EnumChatFormatting.RESET
+ + " D: "
+ + EnumChatFormatting.AQUA
+ + aWorld.provider.dimensionId
+ + EnumChatFormatting.RESET
+ + " -----");
+ try {
+ tList.add(
+ GT_Utility.trans("162", "Name: ") + EnumChatFormatting.BLUE
+ + ((tTileEntity instanceof IInventory inv) ? inv.getInventoryName() : tBlock.getUnlocalizedName())
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("163", " MetaData: ")
+ + EnumChatFormatting.AQUA
+ + aWorld.getBlockMetadata(aX, aY, aZ)
+ + EnumChatFormatting.RESET);
+ tList.add(
+ GT_Utility.trans("164", "Hardness: ") + EnumChatFormatting.YELLOW
+ + tBlock.getBlockHardness(aWorld, aX, aY, aZ)
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("165", " Blast Resistance: ")
+ + EnumChatFormatting.YELLOW
+ + tBlock
+ .getExplosionResistance(aPlayer, aWorld, aX, aY, aZ, aPlayer.posX, aPlayer.posY, aPlayer.posZ)
+ + EnumChatFormatting.RESET);
+ if (tBlock.isBeaconBase(aWorld, aX, aY, aZ, aX, aY + 1, aZ)) tList.add(
+ EnumChatFormatting.GOLD + GT_Utility.trans("166", "Is valid Beacon Pyramid Material")
+ + EnumChatFormatting.RESET);
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private static int addFluidHandlerInfo(ForgeDirection side, ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof IFluidHandler fluidHandler) {
+ rEUAmount += 500;
+ final FluidTankInfo[] tTanks = fluidHandler.getTankInfo(side);
+ if (tTanks != null) for (byte i = 0; i < tTanks.length; i++) {
+ tList.add(
+ GT_Utility.trans("167", "Tank ") + i
+ + ": "
+ + EnumChatFormatting.GREEN
+ + formatNumbers((tTanks[i].fluid == null ? 0 : tTanks[i].fluid.amount))
+ + EnumChatFormatting.RESET
+ + " L / "
+ + EnumChatFormatting.YELLOW
+ + formatNumbers(tTanks[i].capacity)
+ + EnumChatFormatting.RESET
+ + " L "
+ + EnumChatFormatting.GOLD
+ + getFluidName(tTanks[i].fluid, true)
+ + EnumChatFormatting.RESET);
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addDebuggableBlockInfo(EntityPlayer aPlayer, int aX, int aY, int aZ, ArrayList<String> tList,
+ Block tBlock) {
+ int rEUAmount = 0;
+ try {
+ if (tBlock instanceof IDebugableBlock debugableBlock) {
+ rEUAmount += 500;
+ final ArrayList<String> temp = debugableBlock.getDebugInfo(aPlayer, aX, aY, aZ, 3);
+ if (temp != null) tList.addAll(temp);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static void addPollutionInfo(ArrayList<String> tList, Chunk currentChunk) {
+ if (GT_Pollution.hasPollution(currentChunk)) {
+ tList.add(
+ GT_Utility.trans("202", "Pollution in Chunk: ") + EnumChatFormatting.RED
+ + formatNumbers(GT_Pollution.getPollution(currentChunk))
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("203", " gibbl"));
+ } else {
+ tList.add(
+ EnumChatFormatting.GREEN + GT_Utility.trans("204", "No Pollution in Chunk! HAYO!")
+ + EnumChatFormatting.RESET);
+ }
+ }
+
+ private static void addUndergroundFluidInfo(EntityPlayer aPlayer, ArrayList<String> tList, Chunk currentChunk) {
+ if (aPlayer.capabilities.isCreativeMode) {
+ final 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 + GT_Utility.trans("201", "Nothing")
+ + EnumChatFormatting.RESET
+ + ": "
+ + EnumChatFormatting.YELLOW
+ + '0'
+ + EnumChatFormatting.RESET
+ + " L");
+ }
+ }
+
+ private static int addForestryLeavesInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof forestry.arboriculture.tiles.TileLeaves tileLeaves) {
+ final forestry.api.arboriculture.ITree tree = tileLeaves.getTree();
+ if (tree != null) {
+ rEUAmount += 1000;
+ if (!tree.isAnalyzed()) tree.analyze();
+ tree.addTooltip(tList);
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addIC2CropInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ic2.api.crops.ICropTile crop) {
+ rEUAmount += 1000;
+ if (crop.getScanLevel() < 4) crop.setScanLevel((byte) 4);
+ if (crop.getCrop() != null) {
+ tList.add(
+ GT_Utility.trans("187", "Type -- Crop-Name: ") + crop.getCrop()
+ .name()
+ + GT_Utility.trans("188", " Growth: ")
+ + crop.getGrowth()
+ + GT_Utility.trans("189", " Gain: ")
+ + crop.getGain()
+ + GT_Utility.trans("190", " Resistance: ")
+ + crop.getResistance());
+ }
+ tList.add(
+ GT_Utility.trans("191", "Plant -- Fertilizer: ") + crop.getNutrientStorage()
+ + GT_Utility.trans("192", " Water: ")
+ + crop.getHydrationStorage()
+ + GT_Utility.trans("193", " Weed-Ex: ")
+ + crop.getWeedExStorage()
+ + GT_Utility.trans("194", " Scan-Level: ")
+ + crop.getScanLevel());
+ tList.add(
+ GT_Utility.trans("195", "Environment -- Nutrients: ") + crop.getNutrients()
+ + GT_Utility.trans("196", " Humidity: ")
+ + crop.getHumidity()
+ + GT_Utility.trans("197", " Air-Quality: ")
+ + crop.getAirQuality());
+ if (crop.getCrop() != null) {
+ final StringBuilder tStringB = new StringBuilder();
+ for (final String tAttribute : crop.getCrop()
+ .attributes()) {
+ tStringB.append(", ")
+ .append(tAttribute);
+ }
+ final String tString = tStringB.toString();
+ tList.add(GT_Utility.trans("198", "Attributes:") + tString.replaceFirst(",", E));
+ tList.add(
+ GT_Utility.trans("199", "Discovered by: ") + crop.getCrop()
+ .discoveredBy());
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static void addDeviceInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ try {
+ if (tTileEntity instanceof IGregTechDeviceInformation info && info.isGivingInformation()) {
+ tList.addAll(Arrays.asList(info.getInfoData()));
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private static void addOwnerInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ try {
+ if (tTileEntity instanceof IGregTechTileEntity gtTE) {
+ tList.add(
+ GT_Utility.trans("186", "Owned by: ") + EnumChatFormatting.BLUE
+ + gtTE.getOwnerName()
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private static void addEnergyContainerInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ try {
+ if (tTileEntity instanceof IBasicEnergyContainer energyContainer && energyContainer.getEUCapacity() > 0) {
+ tList.add(
+ GT_Utility.trans("179", "Max IN: ") + EnumChatFormatting.RED
+ + formatNumbers(energyContainer.getInputVoltage())
+ + " ("
+ + GT_Values.VN[getTier(energyContainer.getInputVoltage())]
+ + ") "
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("182", " EU at ")
+ + EnumChatFormatting.RED
+ + formatNumbers(energyContainer.getInputAmperage())
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("183", " A"));
+ tList.add(
+ GT_Utility.trans("181", "Max OUT: ") + EnumChatFormatting.RED
+ + formatNumbers(energyContainer.getOutputVoltage())
+ + " ("
+ + GT_Values.VN[getTier(energyContainer.getOutputVoltage())]
+ + ") "
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("182", " EU at ")
+ + EnumChatFormatting.RED
+ + formatNumbers(energyContainer.getOutputAmperage())
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("183", " A"));
+ tList.add(
+ GT_Utility.trans("184", "Energy: ") + EnumChatFormatting.GREEN
+ + formatNumbers(energyContainer.getStoredEU())
+ + EnumChatFormatting.RESET
+ + " EU / "
+ + EnumChatFormatting.YELLOW
+ + formatNumbers(energyContainer.getEUCapacity())
+ + EnumChatFormatting.RESET
+ + " EU");
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private static int addCoverableInfo(ForgeDirection side, ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ICoverable coverable) {
+ rEUAmount += 300;
+ final String tString = coverable.getCoverInfoAtSide(side)
+ .getBehaviorDescription();
+ if (tString != null && !tString.equals(E)) tList.add(tString);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addMachineProgressInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof IMachineProgress progress) {
+ if (progress.isAllowedToWork() && !progress.hasThingsToDo()) {
+ tList.add(EnumChatFormatting.RED + "Disabled." + EnumChatFormatting.RESET);
+ }
+ if (progress.wasShutdown() && isStringValid(
+ progress.getLastShutDownReason()
+ .getDisplayString())) {
+ tList.add(
+ progress.getLastShutDownReason()
+ .getDisplayString());
+ }
+ rEUAmount += 400;
+ int tValue = 0;
+ if (0 < (tValue = progress.getMaxProgress())) tList.add(
+ GT_Utility.trans("178", "Progress/Load: ") + EnumChatFormatting.GREEN
+ + formatNumbers(progress.getProgress())
+ + EnumChatFormatting.RESET
+ + " / "
+ + EnumChatFormatting.YELLOW
+ + formatNumbers(tValue)
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addUpgradableMachineInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof IUpgradableMachine upgradableMachine) {
+ rEUAmount += 500;
+ if (upgradableMachine.hasMufflerUpgrade()) tList.add(
+ EnumChatFormatting.GREEN + GT_Utility.trans("177", "Has Muffler Upgrade")
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addIC2EnergyStorageInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ic2.api.tile.IEnergyStorage storage) {
+ rEUAmount += 200;
+ tList.add(
+ GT_Utility.trans("176", "Contained Energy: ") + EnumChatFormatting.YELLOW
+ + formatNumbers(storage.getStored())
+ + EnumChatFormatting.RESET
+ + " EU / "
+ + EnumChatFormatting.YELLOW
+ + formatNumbers(storage.getCapacity())
+ + EnumChatFormatting.RESET
+ + " EU");
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addIC2EnergyConductorInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ic2.api.energy.tile.IEnergyConductor conductor) {
+ rEUAmount += 200;
+ tList.add(
+ GT_Utility.trans("175", "Conduction Loss: ") + EnumChatFormatting.YELLOW
+ + conductor.getConductionLoss()
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addIC2WrenchableInfo(EntityPlayer aPlayer, ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ic2.api.tile.IWrenchable wrenchable) {
+ rEUAmount += 100;
+ tList.add(
+ GT_Utility.trans("171", "Facing: ") + EnumChatFormatting.GREEN
+ + wrenchable.getFacing()
+ + EnumChatFormatting.RESET
+ + GT_Utility.trans("172", " / Chance: ")
+ + EnumChatFormatting.YELLOW
+ + (wrenchable.getWrenchDropRate() * 100)
+ + EnumChatFormatting.RESET
+ + "%");
+ tList.add(
+ wrenchable.wrenchCanRemove(aPlayer)
+ ? EnumChatFormatting.GREEN + GT_Utility.trans("173", "You can remove this with a Wrench")
+ + EnumChatFormatting.RESET
+ : EnumChatFormatting.RED + GT_Utility.trans("174", "You can NOT remove this with a Wrench")
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addAlignmentInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof IAlignmentProvider alignmentProvider) {
+ final IAlignment tAlignment = alignmentProvider.getAlignment();
+ if (tAlignment != null) {
+ rEUAmount += 100;
+ tList.add(
+ GT_Utility.trans("219", "Extended Facing: ") + EnumChatFormatting.GREEN
+ + tAlignment.getExtendedFacing()
+ + EnumChatFormatting.RESET);
+ }
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ private static int addReactorInfo(ArrayList<String> tList, TileEntity tTileEntity) {
+ int rEUAmount = 0;
+ try {
+ if (tTileEntity instanceof ic2.api.reactor.IReactor reactor) {
+ rEUAmount += 500;
+ tList.add(
+ GT_Utility.trans("168", "Heat: ") + EnumChatFormatting.GREEN
+ + formatNumbers(reactor.getHeat())
+ + EnumChatFormatting.RESET
+ + " / "
+ + EnumChatFormatting.YELLOW
+ + formatNumbers(reactor.getMaxHeat())
+ + EnumChatFormatting.RESET);
+ tList.add(
+ GT_Utility.trans("169", "HEM: ") + EnumChatFormatting.YELLOW
+ + reactor.getHeatEffectModifier()
+ + EnumChatFormatting.RESET);
+ }
+ } catch (Throwable e) {
+ if (D1) e.printStackTrace(GT_Log.err);
+ }
+ return rEUAmount;
+ }
+
+ public static String trans(String aKey, String aEnglish) {
+ return GT_LanguageManager.addStringLocalization("Interaction_DESCRIPTION_Index_" + aKey, aEnglish);
+ }
+
+ public static String getTrans(String aKey) {
+ return GT_LanguageManager.getTranslation("Interaction_DESCRIPTION_Index_" + aKey);
+ }
+
+ /**
+ * @return an Array containing the X and the Y Coordinate of the clicked Point, with the top left Corner as Origin,
+ * like on the Texture Sheet. return values should always be between [0.0F and 0.99F].
+ */
+ // TODO: use clamp()
+ public static float[] getClickedFacingCoords(ForgeDirection side, float aX, float aY, float aZ) {
+ return switch (side) {
+ case DOWN -> new float[] { Math.min(0.99F, Math.max(0, 1 - aX)), Math.min(0.99F, Math.max(0, aZ)) };
+ case UP -> new float[] { Math.min(0.99F, Math.max(0, aX)), Math.min(0.99F, Math.max(0, aZ)) };
+ case NORTH -> new float[] { Math.min(0.99F, Math.max(0, 1 - aX)), Math.min(0.99F, Math.max(0, 1 - aY)) };
+ case SOUTH -> new float[] { Math.min(0.99F, Math.max(0, aX)), Math.min(0.99F, Math.max(0, 1 - aY)) };
+ case WEST -> new float[] { Math.min(0.99F, Math.max(0, aZ)), Math.min(0.99F, Math.max(0, 1 - aY)) };
+ case EAST -> new float[] { Math.min(0.99F, Math.max(0, 1 - aZ)), Math.min(0.99F, Math.max(0, 1 - aY)) };
+ default -> new float[] { 0.5F, 0.5F };
+ };
+ }
+
+ /**
+ * This Function determines the direction a Block gets when being Wrenched. returns -1 if invalid. Even though that
+ * could never happen.
+ */
+ public static ForgeDirection determineWrenchingSide(ForgeDirection side, float aX, float aY, float aZ) {
+ ForgeDirection tBack = side.getOpposite();
+ switch (side) {
+ case DOWN, UP -> {
+ if (aX < 0.25) {
+ if (aZ < 0.25) return tBack;
+ if (aZ > 0.75) return tBack;
+ return WEST;
+ }
+ if (aX > 0.75) {
+ if (aZ < 0.25) return tBack;
+ if (aZ > 0.75) return tBack;
+ return EAST;
+ }
+ if (aZ < 0.25) return NORTH;
+ if (aZ > 0.75) return SOUTH;
+ return side;
+ }
+ case NORTH, SOUTH -> {
+ if (aX < 0.25) {
+ if (aY < 0.25) return tBack;
+ if (aY > 0.75) return tBack;
+ return WEST;
+ }
+ if (aX > 0.75) {
+ if (aY < 0.25) return tBack;
+ if (aY > 0.75) return tBack;
+ return EAST;
+ }
+ if (aY < 0.25) return DOWN;
+ if (aY > 0.75) return UP;
+ return side;
+ }
+ case WEST, EAST -> {
+ if (aZ < 0.25) {
+ if (aY < 0.25) return tBack;
+ if (aY > 0.75) return tBack;
+ return NORTH;
+ }
+ if (aZ > 0.75) {
+ if (aY < 0.25) return tBack;
+ if (aY > 0.75) return tBack;
+ return SOUTH;
+ }
+ if (aY < 0.25) return DOWN;
+ if (aY > 0.75) return UP;
+ return side;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ private static DecimalFormat getDecimalFormat() {
+ return decimalFormatters.computeIfAbsent(Locale.getDefault(Locale.Category.FORMAT), locale -> {
+ DecimalFormat numberFormat = new DecimalFormat(); // uses the necessary locale inside anyway
+ numberFormat.setGroupingUsed(true);
+ numberFormat.setMaximumFractionDigits(2);
+ numberFormat.setRoundingMode(RoundingMode.HALF_UP);
+ DecimalFormatSymbols decimalFormatSymbols = numberFormat.getDecimalFormatSymbols();
+ decimalFormatSymbols.setGroupingSeparator(','); // Use sensible separator for best clarity.
+ numberFormat.setDecimalFormatSymbols(decimalFormatSymbols);
+ return numberFormat;
+ });
+ }
+
+ public static String formatNumbers(BigInteger aNumber) {
+ return getDecimalFormat().format(aNumber);
+ }
+
+ public static String formatNumbers(long aNumber) {
+ return getDecimalFormat().format(aNumber);
+ }
+
+ public static String formatNumbers(double aNumber) {
+ return getDecimalFormat().format(aNumber);
+ }
+
+ /*
+ * Check if stack has enough items of given type and subtract from stack, if there's no creative or 111 stack.
+ */
+ public static boolean consumeItems(EntityPlayer player, ItemStack stack, Item item, int count) {
+ if (stack != null && stack.getItem() == item && stack.stackSize >= count) {
+ if ((!player.capabilities.isCreativeMode) && (stack.stackSize != 111)) stack.stackSize -= count;
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * Check if stack has enough items of given gregtech material (will be oredicted) and subtract from stack, if
+ * there's no creative or 111 stack.
+ */
+ public static boolean consumeItems(EntityPlayer player, ItemStack stack, gregtech.api.enums.Materials mat,
+ int count) {
+ if (stack != null && GT_OreDictUnificator.getItemData(stack).mMaterial.mMaterial == mat
+ && stack.stackSize >= count) {
+ if ((!player.capabilities.isCreativeMode) && (stack.stackSize != 111)) stack.stackSize -= count;
+ return true;
+ }
+ return false;
+ }
+
+ public static ArrayList<String> sortByValueToList(Map<String, Integer> map) {
+ List<Map.Entry<String, Integer>> list = new LinkedList<>(map.entrySet());
+ list.sort((o1, o2) -> o2.getValue() - o1.getValue());
+
+ ArrayList<String> result = new ArrayList<>();
+ for (Map.Entry<String, Integer> e : list) result.add(e.getKey());
+ return result;
+ }
+
+ public static String joinListToString(List<String> list) {
+ StringBuilder result = new StringBuilder(32);
+ for (String s : list) result.append(result.length() == 0 ? s : '|' + s);
+ return result.toString();
+ }
+
+ public static ItemStack getIntegratedCircuit(int config) {
+ return ItemList.Circuit_Integrated.getWithDamage(0, config);
+ }
+
+ public static float getBlockHardnessAt(World aWorld, int aX, int aY, int aZ) {
+ return aWorld.getBlock(aX, aY, aZ)
+ .getBlockHardness(aWorld, aX, aY, aZ);
+ }
+
+ public static FakePlayer getFakePlayer(IGregTechTileEntity aBaseMetaTileEntity) {
+ if (aBaseMetaTileEntity.getWorld() instanceof WorldServer) {
+ return FakePlayerFactory.get(
+ (WorldServer) aBaseMetaTileEntity.getWorld(),
+ new GameProfile(aBaseMetaTileEntity.getOwnerUuid(), aBaseMetaTileEntity.getOwnerName()));
+ }
+ return null;
+ }
+
+ public static boolean eraseBlockByFakePlayer(FakePlayer aPlayer, int aX, int aY, int aZ, boolean isSimulate) {
+ if (aPlayer == null) return false;
+ World aWorld = aPlayer.worldObj;
+ BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(
+ aX,
+ aY,
+ aZ,
+ aWorld,
+ aWorld.getBlock(aX, aY, aZ),
+ aWorld.getBlockMetadata(aX, aY, aZ),
+ aPlayer);
+ MinecraftForge.EVENT_BUS.post(event);
+ if (!event.isCanceled()) {
+ if (!isSimulate) return aWorld.setBlockToAir(aX, aY, aZ);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean setBlockByFakePlayer(FakePlayer aPlayer, int aX, int aY, int aZ, Block aBlock, int aMeta,
+ boolean isSimulate) {
+ if (aPlayer == null) return false;
+ World aWorld = aPlayer.worldObj;
+ BlockEvent.PlaceEvent event = ForgeEventFactory
+ .onPlayerBlockPlace(aPlayer, new BlockSnapshot(aWorld, aX, aY, aZ, aBlock, aMeta), UNKNOWN);
+ if (!event.isCanceled()) {
+ if (!isSimulate) return aWorld.setBlock(aX, aY, aZ, aBlock, aMeta, 3);
+ return true;
+ }
+ return false;
+ }
+
+ public static int findMatchingStackInList(List<ItemStack> aStacks, ItemStack aStack) {
+ if (isStackInvalid(aStack)) return -1;
+ for (int i = 0, aStacksSize = aStacks.size(); i < aStacksSize; i++) {
+ ItemStack tStack = aStacks.get(i);
+ if (areStacksEqual(aStack, tStack)) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * @return Supplied collection that doesn't contain invalid MetaTileEntities
+ */
+ public static <T extends Collection<E>, E extends MetaTileEntity> T filterValidMTEs(T metaTileEntities) {
+ metaTileEntities.removeIf(mte -> mte == null || !mte.isValid());
+ return metaTileEntities;
+ }
+
+ public static class ItemNBT {
+
+ public static void setNBT(ItemStack aStack, NBTTagCompound aNBT) {
+ if (aNBT == null) {
+ aStack.setTagCompound(null);
+ return;
+ }
+ ArrayList<String> tTagsToRemove = new ArrayList<>();
+ for (String tKey : aNBT.func_150296_c()) {
+ NBTBase tValue = aNBT.getTag(tKey);
+ if (tValue == null || (tValue instanceof NBTPrimitive && ((NBTPrimitive) tValue).func_150291_c() == 0)
+ || (tValue instanceof NBTTagString && isStringInvalid(((NBTTagString) tValue).func_150285_a_())))
+ tTagsToRemove.add(tKey);
+ }
+ for (String tKey : tTagsToRemove) aNBT.removeTag(tKey);
+ aStack.setTagCompound(aNBT.hasNoTags() ? null : aNBT);
+ }
+
+ public static NBTTagCompound getNBT(ItemStack aStack) {
+ NBTTagCompound rNBT = aStack.getTagCompound();
+ return rNBT == null ? new NBTTagCompound() : rNBT;
+ }
+
+ public static void setPunchCardData(ItemStack aStack, String aPunchCardData) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ tNBT.setString("GT.PunchCardData", aPunchCardData);
+ setNBT(aStack, tNBT);
+ }
+
+ public static String getPunchCardData(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ return tNBT.getString("GT.PunchCardData");
+ }
+
+ public static void setLighterFuel(ItemStack aStack, long aFuel) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ tNBT.setLong("GT.LighterFuel", aFuel);
+ setNBT(aStack, tNBT);
+ }
+
+ public static long getLighterFuel(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ return tNBT.getLong("GT.LighterFuel");
+ }
+
+ public static void setMapID(ItemStack aStack, short aMapID) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ tNBT.setShort("map_id", aMapID);
+ setNBT(aStack, tNBT);
+ }
+
+ public static short getMapID(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ if (!tNBT.hasKey("map_id")) return -1;
+ return tNBT.getShort("map_id");
+ }
+
+ public static void setBookTitle(ItemStack aStack, String aTitle) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ tNBT.setString("title", aTitle);
+ setNBT(aStack, tNBT);
+ }
+
+ public static String getBookTitle(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ return tNBT.getString("title");
+ }
+
+ public static void setBookAuthor(ItemStack aStack, String aAuthor) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ tNBT.setString("author", aAuthor);
+ setNBT(aStack, tNBT);
+ }
+
+ public static String getBookAuthor(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ return tNBT.getString("author");
+ }
+
+ public static void setProspectionData(ItemStack aStack, int aX, int aY, int aZ, int aDim, FluidStack aFluid,
+ String... aOres) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ StringBuilder tData = new StringBuilder(aX + "," + aY + "," + aZ + "," + aDim + ",");
+ if (aFluid != null) tData.append(aFluid.amount)
+ .append(",")
+ .append(aFluid.getLocalizedName())
+ .append(","); // TODO
+ // CHECK
+ // IF
+ // THAT
+ // /5000
+ // is
+ // needed
+ // (Not
+ // needed)
+ for (String tString : aOres) {
+ tData.append(tString)
+ .append(",");
+ }
+ tNBT.setString("prospection", tData.toString());
+ setNBT(aStack, tNBT);
+ }
+
+ public static void setAdvancedProspectionData(byte aTier, ItemStack aStack, int aX, short aY, int aZ, int aDim,
+ ArrayList<String> aOils, ArrayList<String> aOres, int aRadius) {
+
+ setBookTitle(aStack, "Raw Prospection Data");
+
+ NBTTagCompound tNBT = getNBT(aStack);
+
+ tNBT.setByte("prospection_tier", aTier);
+ tNBT.setString("prospection_pos", "Dim: " + aDim + "\nX: " + aX + " Y: " + aY + " Z: " + aZ);
+
+ // ores
+ Collections.sort(aOres);
+ tNBT.setString("prospection_ores", joinListToString(aOres));
+
+ // oils
+ ArrayList<String> tOilsTransformed = new ArrayList<>(aOils.size());
+ for (String aStr : aOils) {
+ String[] aStats = aStr.split(",");
+ tOilsTransformed.add(aStats[0] + ": " + aStats[1] + "L " + aStats[2]);
+ }
+
+ tNBT.setString("prospection_oils", joinListToString(tOilsTransformed));
+
+ String tOilsPosStr = "X: " + Math.floorDiv(aX, 16 * 8) * 16 * 8
+ + " Z: "
+ + Math.floorDiv(aZ, 16 * 8) * 16 * 8
+ + "\n";
+ int xOff = aX - Math.floorDiv(aX, 16 * 8) * 16 * 8;
+ xOff = xOff / 16;
+ int xOffRemain = 7 - xOff;
+
+ int zOff = aZ - Math.floorDiv(aZ, 16 * 8) * 16 * 8;
+ zOff = zOff / 16;
+ int zOffRemain = 7 - zOff;
+
+ for (; zOff > 0; zOff--) {
+ tOilsPosStr = tOilsPosStr.concat("--------\n");
+ }
+ for (; xOff > 0; xOff--) {
+ tOilsPosStr = tOilsPosStr.concat("-");
+ }
+
+ tOilsPosStr = tOilsPosStr.concat("P");
+
+ for (; xOffRemain > 0; xOffRemain--) {
+ tOilsPosStr = tOilsPosStr.concat("-");
+ }
+ tOilsPosStr = tOilsPosStr.concat("\n");
+ for (; zOffRemain > 0; zOffRemain--) {
+ tOilsPosStr = tOilsPosStr.concat("--------\n");
+ }
+ tOilsPosStr = tOilsPosStr.concat(
+ " X: " + (Math.floorDiv(aX, 16 * 8) + 1) * 16 * 8
+ + " Z: "
+ + (Math.floorDiv(aZ, 16 * 8) + 1) * 16 * 8); // +1 oilfied to find bottomright of [5]
+
+ tNBT.setString("prospection_oils_pos", tOilsPosStr);
+
+ tNBT.setString("prospection_radius", String.valueOf(aRadius));
+
+ setNBT(aStack, tNBT);
+ }
+
+ public static void convertProspectionData(ItemStack aStack) {
+ NBTTagCompound tNBT = getNBT(aStack);
+ byte tTier = tNBT.getByte("prospection_tier");
+
+ if (tTier == 0) { // basic prospection data
+ String tData = tNBT.getString("prospection");
+ String[] tDataArray = tData.split(",");
+ if (tDataArray.length > 6) {
+ tNBT.setString(
+ "author",
+ " Dim: " + tDataArray[3]
+ + "X: "
+ + tDataArray[0]
+ + " Y: "
+ + tDataArray[1]
+ + " Z: "
+ + tDataArray[2]);
+ NBTTagList tNBTList = new NBTTagList();
+ StringBuilder tOres = new StringBuilder(" Prospected Ores: ");
+ for (int i = 6; tDataArray.length > i; i++) {
+ tOres.append(tDataArray[i])
+ .append(" ");
+ }
+ tNBTList.appendTag(
+ new NBTTagString(
+ "Tier " + tTier
+ + " Prospecting Data From: X"
+ + tDataArray[0]
+ + " Z:"
+ + tDataArray[2]
+ + " Dim:"
+ + tDataArray[3]
+ + " Produces "
+ + tDataArray[4]
+ + "L "
+ + tDataArray[5]
+ + " "
+ + tOres));
+ tNBT.setTag("pages", tNBTList);
+ }
+ } else { // advanced prospection data
+ String tPos = tNBT.getString("prospection_pos");
+ String tRadius = tNBT.getString("prospection_radius");
+
+ String tOresStr = tNBT.getString("prospection_ores");
+ String tOilsStr = tNBT.getString("prospection_oils");
+ String tOilsPosStr = tNBT.getString("prospection_oils_pos");
+
+ String[] tOres = tOresStr.isEmpty() ? null : tOresStr.split("\\|");
+ String[] tOils = tOilsStr.isEmpty() ? null : tOilsStr.split("\\|");
+
+ NBTTagList tNBTList = new NBTTagList();
+
+ String tPageText = "Prospector report\n" + tPos
+ + "\n\n"
+ + "Oils: "
+ + (tOils != null ? tOils.length : 0)
+ + "\n\n"
+ + "Ores within "
+ + tRadius
+ + " blocks\n\n"
+ + "Location is center of orevein\n\n"
+ + "Check NEI to confirm orevein type";
+ tNBTList.appendTag(new NBTTagString(tPageText));
+
+ if (tOres != null) fillBookWithList(tNBTList, "Ores Found %s\n\n", "\n", 7, tOres);
+
+ if (tOils != null) fillBookWithList(tNBTList, "Oils%s\n\n", "\n", 9, tOils);
+
+ tPageText = """
+ Oil notes
+
+ Prospects from NW to SE 576 chunks(9 8x8 oilfields)
+ around and gives min-max amount
+
+ [1][2][3]
+ [4][5][6]
+ [7][8][9]
+
+ [5] - Prospector in this 8x8 area""";
+ tNBTList.appendTag(new NBTTagString(tPageText));
+
+ tPageText = "Corners of [5] are \n" + tOilsPosStr + "\n" + "P - Prospector in 8x8 field";
+ tNBTList.appendTag(new NBTTagString(tPageText));
+
+ tNBT.setString("author", tPos.replace("\n", " "));
+ tNBT.setTag("pages", tNBTList);
+ }
+ setNBT(aStack, tNBT);
+ }
+
+ public static void fillBookWithList(NBTTagList aBook, String aPageHeader, String aListDelimiter,
+ int aItemsPerPage, String[] list) {
+ String aPageFormatter = " %d/%d";
+ int tTotalPages = list.length / aItemsPerPage + (list.length % aItemsPerPage > 0 ? 1 : 0);
+ int tPage = 0;
+ StringBuilder tPageText;
+ do {
+ tPageText = new StringBuilder();
+ for (int i = tPage * aItemsPerPage; i < (tPage + 1) * aItemsPerPage && i < list.length; i += 1)
+ tPageText.append((tPageText.length() == 0) ? "" : aListDelimiter)
+ .append(list[i]);
+
+ if (tPageText.length() > 0) {
+ String tPageCounter = tTotalPages > 1 ? String.format(aPageFormatter, tPage + 1, tTotalPages) : "";
+ NBTTagString tPageTag = new NBTTagString(String.format(aPageHeader, tPageCounter) + tPageText);
+ aBook.appendTag(tPageTag);
+ }
+
+ ++tPage;
+ } while (tPageText.length() > 0);
+ }
+
+ public static void addEnchantment(ItemStack aStack, Enchantment aEnchantment, int aLevel) {
+ NBTTagCompound tNBT = getNBT(aStack), tEnchantmentTag;
+ if (!tNBT.hasKey("ench", 9)) tNBT.setTag("ench", new NBTTagList());
+ NBTTagList tList = tNBT.getTagList("ench", 10);
+
+ boolean temp = true;
+
+ for (int i = 0; i < tList.tagCount(); i++) {
+ tEnchantmentTag = tList.getCompoundTagAt(i);
+ if (tEnchantmentTag.getShort("id") == aEnchantment.effectId) {
+ tEnchantmentTag.setShort("id", (short) aEnchantment.effectId);
+ tEnchantmentTag.setShort("lvl", (byte) aLevel);
+ temp = false;
+ break;
+ }
+ }
+
+ if (temp) {
+ tEnchantmentTag = new NBTTagCompound();
+ tEnchantmentTag.setShort("id", (short) aEnchantment.effectId);
+ tEnchantmentTag.setShort("lvl", (byte) aLevel);
+ tList.appendTag(tEnchantmentTag);
+ }
+ aStack.setTagCompound(tNBT);
+ }
+ }
+
+ /**
+ * THIS IS BULLSHIT!!! WHY DO I HAVE TO DO THIS SHIT JUST TO HAVE ENCHANTS PROPERLY!?!
+ */
+ public static class GT_EnchantmentHelper {
+
+ private static final BullshitIteratorA mBullshitIteratorA = new BullshitIteratorA();
+ private static final BullshitIteratorB mBullshitIteratorB = new BullshitIteratorB();
+
+ private static void applyBullshit(IBullshit aBullshitModifier, ItemStack aStack) {
+ if (aStack != null) {
+ NBTTagList nbttaglist = aStack.getEnchantmentTagList();
+ if (nbttaglist != null) {
+ try {
+ for (int i = 0; i < nbttaglist.tagCount(); ++i) {
+ short short1 = nbttaglist.getCompoundTagAt(i)
+ .getShort("id");
+ short short2 = nbttaglist.getCompoundTagAt(i)
+ .getShort("lvl");
+ if (Enchantment.enchantmentsList[short1] != null)
+ aBullshitModifier.calculateModifier(Enchantment.enchantmentsList[short1], short2);
+ }
+ } catch (Throwable e) {
+ /**/
+ }
+ }
+ }
+ }
+
+ private static void applyArrayOfBullshit(IBullshit aBullshitModifier, ItemStack[] aStacks) {
+ for (ItemStack itemstack : aStacks) {
+ applyBullshit(aBullshitModifier, itemstack);
+ }
+ }
+
+ public static void applyBullshitA(EntityLivingBase aPlayer, Entity aEntity, ItemStack aStack) {
+ mBullshitIteratorA.mPlayer = aPlayer;
+ mBullshitIteratorA.mEntity = aEntity;
+ if (aPlayer != null) applyArrayOfBullshit(mBullshitIteratorA, aPlayer.getLastActiveItems());
+ if (aStack != null) applyBullshit(mBullshitIteratorA, aStack);
+ }
+
+ public static void applyBullshitB(EntityLivingBase aPlayer, Entity aEntity, ItemStack aStack) {
+ mBullshitIteratorB.mPlayer = aPlayer;
+ mBullshitIteratorB.mEntity = aEntity;
+ if (aPlayer != null) applyArrayOfBullshit(mBullshitIteratorB, aPlayer.getLastActiveItems());
+ if (aStack != null) applyBullshit(mBullshitIteratorB, aStack);
+ }
+
+ interface IBullshit {
+
+ void calculateModifier(Enchantment aEnchantment, int aLevel);
+ }
+
+ static final class BullshitIteratorA implements IBullshit {
+
+ public EntityLivingBase mPlayer;
+ public Entity mEntity;
+
+ BullshitIteratorA() {}
+
+ @Override
+ public void calculateModifier(Enchantment aEnchantment, int aLevel) {
+ aEnchantment.func_151367_b(mPlayer, mEntity, aLevel);
+ }
+ }
+
+ static final class BullshitIteratorB implements IBullshit {
+
+ public EntityLivingBase mPlayer;
+ public Entity mEntity;
+
+ BullshitIteratorB() {}
+
+ @Override
+ public void calculateModifier(Enchantment aEnchantment, int aLevel) {
+ aEnchantment.func_151368_a(mPlayer, mEntity, aLevel);
+ }
+ }
+ }
+
+ public static String toSubscript(long no) {
+ char[] chars = Long.toString(no)
+ .toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ chars[i] += 8272;
+ }
+ return new String(chars);
+ }
+
+ public static boolean isPartOfMaterials(ItemStack aStack, Materials aMaterials) {
+ return GT_OreDictUnificator.getAssociation(aStack) != null
+ && GT_OreDictUnificator.getAssociation(aStack).mMaterial.mMaterial.equals(aMaterials);
+ }
+
+ public static boolean isPartOfOrePrefix(ItemStack aStack, OrePrefixes aPrefix) {
+ return GT_OreDictUnificator.getAssociation(aStack) != null
+ && GT_OreDictUnificator.getAssociation(aStack).mPrefix.equals(aPrefix);
+ }
+
+ public static final ImmutableSet<String> ORE_BLOCK_CLASSES = ImmutableSet.of(
+ "com.github.bartimaeusnek.bartworks.system.material.BW_MetaGenerated_Ores",
+ "com.github.bartimaeusnek.bartworks.system.material.BW_MetaGenerated_SmallOres",
+ "gtPlusPlus.core.block.base.BlockBaseOre");
+
+ public static boolean isOre(Block aBlock, int aMeta) {
+ 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) {
+ int tItem = GT_Utility.stackToInt(aStack);
+ if (sOreTable.containsKey(tItem)) {
+ return sOreTable.get(tItem);
+ }
+ for (int id : OreDictionary.getOreIDs(aStack)) {
+ if (OreDictionary.getOreName(id)
+ .startsWith("ore")) {
+ sOreTable.put(tItem, true);
+ return true;
+ }
+ }
+ sOreTable.put(tItem, false);
+ 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();
+ }
+
+ List<ItemStack> inputs = new ArrayList<>();
+
+ for (Object o : aRecipe) {
+ if (o instanceof ItemStack) {
+ ItemStack toAdd = ((ItemStack) o).copy();
+ inputs.add(toAdd);
+ } else if (o instanceof String) {
+ ItemStack stack = GT_OreDictUnificator.get(o, 1);
+ if (stack == null) {
+ Optional<ItemStack> oStack = OreDictionary.getOres((String) o)
+ .stream()
+ .findAny();
+ if (oStack.isPresent()) {
+ ItemStack copy = oStack.get()
+ .copy();
+ inputs.add(copy);
+ }
+ } else {
+ ItemStack copy = stack.copy();
+ inputs.add(copy);
+ }
+ } else if (o instanceof Item) inputs.add(new ItemStack((Item) o));
+ else if (o instanceof Block) inputs.add(new ItemStack((Block) o));
+ else throw new IllegalStateException("A Recipe contains an invalid input! Output: " + output);
+ }
+
+ inputs.removeIf(x -> x.getItem() instanceof GT_MetaGenerated_Tool);
+
+ return Optional.of(
+ new GT_Recipe(
+ false,
+ new ItemStack[] { output },
+ inputs.toArray(new ItemStack[0]),
+ null,
+ null,
+ null,
+ null,
+ 300,
+ 30,
+ 0));
+ }
+
+ public static Optional<GT_Recipe> reverseShapedRecipe(ItemStack output, Object... aRecipe) {
+ if (output == null) {
+ return Optional.empty();
+ }
+
+ Map<Object, Integer> recipeAsMap = new HashMap<>();
+ Map<Character, Object> ingridients = new HashMap<>();
+ Map<Character, Integer> amounts = new HashMap<>();
+ boolean startFound = false;
+ for (int i = 0, aRecipeLength = aRecipe.length; i < aRecipeLength; i++) {
+ Object o = aRecipe[i];
+ if (!startFound) {
+ if (o instanceof String) {
+ for (Character c : ((String) o).toCharArray()) amounts.merge(c, 1, (a, b) -> ++a);
+ } else if (o instanceof Character) startFound = true;
+ } else if (!(o instanceof Character)) ingridients.put((Character) aRecipe[i - 1], o);
+ }
+ for (Map.Entry<Character, Object> characterObjectEntry : ingridients.entrySet()) {
+ for (Map.Entry<Character, Integer> characterIntegerEntry : amounts.entrySet()) {
+ if (characterObjectEntry.getKey() != characterIntegerEntry.getKey()) continue;
+ recipeAsMap.put(characterObjectEntry.getValue(), characterIntegerEntry.getValue());
+ }
+ }
+ List<ItemStack> inputs = new ArrayList<>();
+
+ for (Map.Entry<Object, Integer> o : recipeAsMap.entrySet()) {
+ final int amount = o.getValue();
+ if (o.getKey() instanceof ItemStack) {
+ ItemStack toAdd = ((ItemStack) o.getKey()).copy();
+ toAdd.stackSize = amount;
+ inputs.add(toAdd);
+ } else if (o.getKey() instanceof String dictName) {
+ // Do not register tools dictName in inputs
+ if (ToolDictNames.contains(dictName)) continue;
+ ItemStack stack = GT_OreDictUnificator.get(dictName, null, amount, false, true);
+ if (stack == null) {
+ Optional<ItemStack> oStack = OreDictionary.getOres(dictName)
+ .stream()
+ .findAny();
+ if (oStack.isPresent()) {
+ ItemStack copy = oStack.get()
+ .copy();
+ copy.stackSize = amount;
+ inputs.add(copy);
+ }
+ } else {
+ ItemStack copy = stack.copy();
+ copy.stackSize = amount;
+ inputs.add(copy);
+ }
+ } else if (o.getKey() instanceof Item) inputs.add(new ItemStack((Item) o.getKey(), amount));
+ else if (o.getKey() instanceof Block) inputs.add(new ItemStack((Block) o.getKey(), amount));
+ else throw new IllegalStateException("A Recipe contains an invalid input! Output: " + output);
+ }
+
+ // Remove tools from inputs in case a recipe has one as a direct Item or ItemStack reference
+ inputs.removeIf(x -> x.getItem() instanceof GT_MetaGenerated_Tool);
+
+ return Optional.of(
+ new GT_Recipe(
+ false,
+ new ItemStack[] { output },
+ inputs.toArray(new ItemStack[0]),
+ null,
+ null,
+ null,
+ null,
+ 300,
+ 30,
+ 0));
+ }
+
+ /**
+ * Add an itemstack to player inventory, or drop on ground if full. Can be called on client but it probably won't
+ * work very well.
+ */
+ public static void addItemToPlayerInventory(EntityPlayer aPlayer, ItemStack aStack) {
+ if (isStackInvalid(aStack)) return;
+ if (!aPlayer.inventory.addItemStackToInventory(aStack) && !aPlayer.worldObj.isRemote) {
+ EntityItem dropItem = aPlayer.entityDropItem(aStack, 0);
+ dropItem.delayBeforeCanPickup = 0;
+ }
+ }
+
+ public static long getNonnullElementCount(Object[] tArray) {
+ return Arrays.stream(tArray)
+ .filter(Objects::nonNull)
+ .count();
+ }
+
+ public static int clamp(int val, int lo, int hi) {
+ return MathHelper.clamp_int(val, lo, hi);
+ }
+
+ public static int ceilDiv(int lhs, int rhs) {
+ return (lhs + rhs - 1) / rhs;
+ }
+
+ public static long ceilDiv(long lhs, long rhs) {
+ return (lhs + rhs - 1) / rhs;
+ }
+
+ /**
+ * Hash an item stack for the purpose of storing hash across launches
+ */
+ public static int persistentHash(ItemStack aStack, boolean aUseStackSize, boolean aUseNBT) {
+ if (aStack == null) return 0;
+ int result = Objects.hashCode(GameRegistry.findUniqueIdentifierFor(aStack.getItem()));
+ result = result * 31 + Items.feather.getDamage(aStack);
+
+ if (aUseStackSize) result = result * 31 + aStack.stackSize;
+ if (aUseNBT) result = result * 31 + Objects.hashCode(aStack.stackTagCompound);
+ return result;
+ }
+
+ /**
+ * Hash an item stack for the purpose of storing hash across launches
+ */
+ public static int persistentHash(FluidStack aStack, boolean aUseStackSize, boolean aUseNBT) {
+ if (aStack == null) return 0;
+ int base = Objects.hashCode(
+ aStack.getFluid()
+ .getName());
+
+ if (aUseStackSize) base = base * 31 + aStack.amount;
+ if (aUseNBT) base = base * 31 + Objects.hashCode(aStack.tag);
+ return base;
+ }
+
+ public static int getCasingTextureIndex(Block block, int meta) {
+ if (block instanceof IHasIndexedTexture) return ((IHasIndexedTexture) block).getTextureIndex(meta);
+ return Textures.BlockIcons.ERROR_TEXTURE_INDEX;
+ }
+
+ public static boolean isCellEmpty(ItemStack itemStack) {
+ if (itemStack == null) return false;
+ ItemStack tStack = ItemList.Cell_Empty.get(1);
+ tStack.stackSize = itemStack.stackSize;
+ return GT_Utility.areStacksEqual(itemStack, tStack);
+ }
+
+ /**
+ * Convert a cell to fluid. If given itemstack does not contain any fluid, return null. Will correctly multiple
+ * output fluid amount if input stack size is greater than 1.
+ */
+ public static FluidStack convertCellToFluid(ItemStack itemStack) {
+ if (itemStack == null) return null;
+ if (getFluidForFilledItem(itemStack, true) != null) {
+ FluidStack fluidStack = getFluidForFilledItem(itemStack, true);
+ if (fluidStack != null) fluidStack.amount = fluidStack.amount * itemStack.stackSize;
+ return fluidStack;
+ }
+ return null;
+ }
+
+ /**
+ * @deprecated typo in method name. use {@link #isAnyIntegratedCircuit(ItemStack)} instead.
+ */
+ @Deprecated
+ public static boolean checkIfSameIntegratedCircuit(ItemStack itemStack) {
+ if (itemStack == null) return false;
+ for (int i = 0; i < 25; i++) if (itemStack.isItemEqual(GT_Utility.getIntegratedCircuit(i))) return true;
+ return false;
+ }
+
+ public static boolean isAnyIntegratedCircuit(ItemStack itemStack) {
+ if (itemStack == null) return false;
+ return itemStack.getItem() == ItemList.Circuit_Integrated.getItem() && 0 <= itemStack.getItemDamage()
+ && itemStack.getItemDamage() < 25;
+ }
+
+ public static byte convertRatioToRedstone(long used, long max, int threshold, boolean inverted) {
+ byte signal;
+ if (used <= 0) { // Empty
+ signal = 0;
+ } else if (used >= max) { // Full
+ signal = 15;
+ } else { // Range 1-14
+ signal = (byte) (1 + (14 * used) / max);
+ }
+
+ if (inverted) {
+ signal = (byte) (15 - signal);
+ }
+
+ if (threshold > 0) {
+ if (inverted && used >= threshold) {
+ return 0;
+ } else if (!inverted && used < threshold) {
+ return 0;
+ }
+ }
+
+ return signal;
+ }
+
+ public static ItemStack getNaniteAsCatalyst(Materials material) {
+ ItemStack aItem = material.getNanite(1);
+ return new ItemStack(aItem.getItem(), 0, aItem.getItemDamage());
+ }
+
+ public static Stream<NBTTagCompound> streamCompounds(NBTTagList list) {
+ if (list == null) return Stream.empty();
+ return IntStream.range(0, list.tagCount())
+ .mapToObj(list::getCompoundTagAt);
+ }
+
+ public static boolean equals(ItemStack[] a, ItemStack[] b) {
+ // because stupid mojang didn't override equals for us
+ if (a == null && b == null) return true;
+ if ((a == null) != (b == null)) return false;
+ if (a.length != b.length) return false;
+ for (int i = 0; i < a.length; i++) {
+ if (!areStacksEqual(a[i], b[i], false)) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Guava ImmutableMap variant of Collectors.toMap. Optimized for serial streams.
+ */
+ public static <T, K, U> Collector<T, ?, ImmutableMap<K, U>> toImmutableMapSerial(
+ Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
+ // petty type inference cannot work out the correct type parameter
+ return Collector.<T, ImmutableMap.Builder<K, U>, ImmutableMap<K, U>>of(
+ ImmutableMap::builder,
+ (b, t) -> b.put(keyMapper.apply(t), valueMapper.apply(t)),
+ (b1, b2) -> b1.putAll(b2.build()),
+ ImmutableMap.Builder::build);
+ }
+
+ public static boolean isArrayEmptyOrNull(Object[] arr) {
+ return arr == null || arr.length == 0;
+ }
+
+ public static boolean isArrayOfLength(Object[] arr, int expectedLength) {
+ return arr != null && arr.length == expectedLength;
+ }
+
+ @SafeVarargs
+ public static <E> Collection<E> concat(Collection<E>... colls) {
+ return concat(Arrays.asList(colls));
+ }
+
+ public static <E> Collection<E> concat(Collection<Collection<E>> colls) {
+ return new ConcatCollection<>(colls);
+ }
+
+ private static class ConcatCollection<E> extends AbstractCollection<E> {
+
+ private final Collection<Collection<E>> colls;
+ private final int size;
+
+ public ConcatCollection(Collection<Collection<E>> lists) {
+ Collection<Collection<E>> colls1 = null;
+ for (Collection<E> list : lists) {
+ if (list == null || list.isEmpty()) {
+ colls1 = lists.stream()
+ .filter(c -> c != null && !c.isEmpty())
+ .collect(Collectors.toList());
+ break;
+ }
+ }
+ if (colls1 == null) colls1 = lists;
+ colls = colls1;
+ int sum = 0;
+ for (Collection<E> list : colls) {
+ sum += list.size();
+ }
+ size = sum;
+ }
+
+ @Nonnull
+ @Override
+ public Iterator<E> iterator() {
+ return colls.stream()
+ .flatMap(Collection::stream)
+ .iterator();
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+ }
+
+ @AutoValue
+ public abstract static class ItemId {
+
+ public static ItemId create(NBTTagCompound tag) {
+ return new AutoValue_GT_Utility_ItemId(
+ Item.getItemById(tag.getShort("item")),
+ tag.getShort("meta"),
+ tag.hasKey("tag", Constants.NBT.TAG_COMPOUND) ? tag.getCompoundTag("tag") : null);
+ }
+
+ /**
+ * This method copies NBT, as it is mutable.
+ */
+ public static ItemId create(ItemStack itemStack) {
+ NBTTagCompound nbt = itemStack.getTagCompound();
+ if (nbt != null) {
+ nbt = (NBTTagCompound) nbt.copy();
+ }
+
+ return new AutoValue_GT_Utility_ItemId(itemStack.getItem(), Items.feather.getDamage(itemStack), nbt);
+ }
+
+ /**
+ * This method copies NBT, as it is mutable.
+ */
+ public static ItemId create(Item item, int metaData, @Nullable NBTTagCompound nbt) {
+ if (nbt != null) {
+ nbt = (NBTTagCompound) nbt.copy();
+ }
+ return new AutoValue_GT_Utility_ItemId(item, metaData, nbt);
+ }
+
+ /**
+ * This method stores metadata as wildcard and NBT as null.
+ */
+ public static ItemId createAsWildcard(ItemStack itemStack) {
+ return new AutoValue_GT_Utility_ItemId(itemStack.getItem(), W, null);
+ }
+
+ /**
+ * This method stores NBT as null.
+ */
+ public static ItemId createWithoutNBT(ItemStack itemStack) {
+ return new AutoValue_GT_Utility_ItemId(itemStack.getItem(), Items.feather.getDamage(itemStack), null);
+ }
+
+ /**
+ * This method does not copy NBT in order to save time. Make sure not to mutate it!
+ */
+ public static ItemId createNoCopy(ItemStack itemStack) {
+ return new AutoValue_GT_Utility_ItemId(
+ itemStack.getItem(),
+ Items.feather.getDamage(itemStack),
+ itemStack.getTagCompound());
+ }
+
+ /**
+ * This method does not copy NBT in order to save time. Make sure not to mutate it!
+ */
+ public static ItemId createNoCopy(Item item, int metaData, @Nullable NBTTagCompound nbt) {
+ return new AutoValue_GT_Utility_ItemId(item, metaData, nbt);
+ }
+
+ protected abstract Item item();
+
+ protected abstract int metaData();
+
+ @Nullable
+ protected abstract NBTTagCompound nbt();
+
+ public NBTTagCompound writeToNBT() {
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setShort("item", (short) Item.getIdFromItem(item()));
+ tag.setShort("meta", (short) metaData());
+ if (nbt() != null) tag.setTag("tag", nbt());
+ return tag;
+ }
+
+ public ItemStack getItemStack() {
+ ItemStack itemStack = new ItemStack(item(), 1, metaData());
+ itemStack.setTagCompound(nbt());
+ return itemStack;
+ }
+ }
+
+ public static int getPlasmaFuelValueInEUPerLiterFromMaterial(Materials material) {
+ return getPlasmaFuelValueInEUPerLiterFromFluid(material.getPlasma(1));
+ }
+
+ public static int getPlasmaFuelValueInEUPerLiterFromFluid(FluidStack aLiquid) {
+ if (aLiquid == null) return 0;
+ GT_Recipe tFuel = RecipeMaps.plasmaFuels.getBackend()
+ .findFuel(aLiquid);
+ if (tFuel != null) return tFuel.mSpecialValue;
+ return 0;
+ }
+
+ public static MovingObjectPosition getPlayerLookingTarget() {
+ // Basically copied from waila, thanks Caedis for such challenge
+ Minecraft mc = Minecraft.getMinecraft();
+ EntityLivingBase viewpoint = mc.renderViewEntity;
+ if (viewpoint == null) return null;
+
+ float reachDistance = mc.playerController.getBlockReachDistance();
+ Vec3 posVec = viewpoint.getPosition(0);
+ Vec3 lookVec = viewpoint.getLook(0);
+ Vec3 modifiedPosVec = posVec
+ .addVector(lookVec.xCoord * reachDistance, lookVec.yCoord * reachDistance, lookVec.zCoord * reachDistance);
+
+ return viewpoint.worldObj.rayTraceBlocks(posVec, modifiedPosVec);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_UtilityClient.java b/src/main/java/gregtech/api/util/GT_UtilityClient.java
new file mode 100644
index 0000000000..398c1f6b41
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_UtilityClient.java
@@ -0,0 +1,52 @@
+package gregtech.api.util;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.item.EnumRarity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+
+import com.google.common.collect.Lists;
+
+import cpw.mods.fml.relauncher.ReflectionHelper;
+
+public class GT_UtilityClient {
+
+ private static final Field isDrawingField = ReflectionHelper
+ .findField(Tessellator.class, "isDrawing", "field_78415_z");
+
+ public static boolean isDrawing(Tessellator tess) {
+ try {
+ return isDrawingField.getBoolean(tess);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public static List<String> getTooltip(ItemStack aStack, boolean aGuiStyle) {
+ try {
+ List<String> tooltip = aStack.getTooltip(
+ Minecraft.getMinecraft().thePlayer,
+ Minecraft.getMinecraft().gameSettings.advancedItemTooltips);
+ if (aGuiStyle) {
+ tooltip.set(
+ 0,
+ (aStack.getRarity() == null ? EnumRarity.common : aStack.getRarity()).rarityColor + tooltip.get(0));
+ for (int i = 1; i < tooltip.size(); i++) {
+ tooltip.set(i, EnumChatFormatting.GRAY + tooltip.get(i));
+ }
+ }
+ return tooltip;
+ } catch (RuntimeException e) {
+ // Collections.singletonList() can not be added to. we don't want that
+ if (aGuiStyle) return Lists.newArrayList(
+ (aStack.getRarity() == null ? EnumRarity.common : aStack.getRarity()).rarityColor
+ + aStack.getDisplayName());
+ return Lists.newArrayList(aStack.getDisplayName());
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Waila.java b/src/main/java/gregtech/api/util/GT_Waila.java
new file mode 100644
index 0000000000..aaa68ba4c7
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Waila.java
@@ -0,0 +1,23 @@
+package gregtech.api.util;
+
+public abstract class GT_Waila {
+
+ public static String getMachineProgressString(boolean isActive, int maxProgresstime, int progresstime) {
+ return getMachineProgressString(isActive, (long) maxProgresstime, (long) progresstime);
+ }
+
+ public static String getMachineProgressString(boolean isActive, long maxProgresstime, long progresstime) {
+
+ if (!isActive) return "Idle";
+
+ StringBuilder ret = new StringBuilder("In progress: ")
+ .append(String.format("%,.2f", (double) progresstime / 20))
+ .append("s / ")
+ .append(String.format("%,.2f", (double) maxProgresstime / 20))
+ .append("s (")
+ .append(GT_Utility.formatNumbers((Math.round((double) progresstime / maxProgresstime * 1000) / 10.0)))
+ .append("%)");
+
+ return ret.toString();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GasSpargingRecipe.java b/src/main/java/gregtech/api/util/GasSpargingRecipe.java
new file mode 100644
index 0000000000..667cc78d85
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GasSpargingRecipe.java
@@ -0,0 +1,103 @@
+package gregtech.api.util;
+
+import net.minecraftforge.fluids.FluidStack;
+
+import gtPlusPlus.api.objects.data.AutoMap;
+import gtPlusPlus.core.util.data.ArrayUtils;
+import gtPlusPlus.core.util.minecraft.ItemUtils;
+import gtPlusPlus.core.util.minecraft.MaterialUtils;
+
+public class GasSpargingRecipe implements Comparable<GasSpargingRecipe> {
+
+ public final FluidStack mInputGas;
+ public final FluidStack mInputSpentFuel;
+ public final FluidStack mOutputSpargedFuel;
+ public final int[] mMaxOutputQuantity;
+ public final FluidStack[] mFluidInputs;
+ public final FluidStack[] mFluidOutputs;
+ public final int mDuration;
+ public final int mEUt;
+
+ public GasSpargingRecipe(FluidStack aSpargeGas, FluidStack aSpentFuel, FluidStack aSpargedFuel,
+ FluidStack[] aOutputs, int[] aMaxOutputQuantity) {
+ mInputGas = aSpargeGas;
+ mInputSpentFuel = aSpentFuel;
+ mOutputSpargedFuel = aSpargedFuel;
+ mFluidInputs = new FluidStack[] { mInputGas, mInputSpentFuel };
+ aOutputs = ArrayUtils.insertElementAtIndex(aOutputs, 0, aSpargeGas);
+ aOutputs = ArrayUtils.insertElementAtIndex(aOutputs, 1, aSpargedFuel);
+ mFluidOutputs = aOutputs;
+ mMaxOutputQuantity = aMaxOutputQuantity;
+ mDuration = 500;
+ mEUt = MaterialUtils.getVoltageForTier(5);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GasSpargingRecipe i) {
+ if (this.mInputGas.equals(i.mInputGas) && this.mInputSpentFuel.equals(i.mInputSpentFuel)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getMaxOutput(int aIndex) {
+ if (aIndex == 0) {
+ return mInputGas.amount * 100;
+ } else if (aIndex == 1) {
+ return mOutputSpargedFuel.amount * 100;
+ }
+ aIndex -= 2;
+ if ((aIndex < 0) || (aIndex >= this.mMaxOutputQuantity.length)) {
+ return 10000;
+ }
+ return this.mMaxOutputQuantity[aIndex];
+ }
+
+ public boolean isValid() {
+ if (mInputGas == null || mInputGas.amount <= 0
+ || mInputSpentFuel == null
+ || mInputSpentFuel.amount <= 0
+ || mFluidOutputs == null
+ || mFluidOutputs.length < 1
+ || mMaxOutputQuantity == null
+ || mMaxOutputQuantity.length < 1
+ || mFluidOutputs.length != mMaxOutputQuantity.length) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean containsInputs(FluidStack aSpargeGas, FluidStack aSpentFuel) {
+ if (aSpargeGas != null && aSpargeGas.getFluid()
+ .equals(this.mInputGas.getFluid())) {
+ if (aSpentFuel != null && aSpentFuel.getFluid()
+ .equals(this.mInputSpentFuel.getFluid())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(GasSpargingRecipe o) {
+ if (o.mFluidOutputs.length > this.mFluidOutputs.length) {
+ return 1;
+ } else if (o.mFluidOutputs.length == this.mFluidOutputs.length) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ public String[] getRecipeInfo() {
+ AutoMap<String> result = new AutoMap<>();
+ result.put("Input " + ItemUtils.getArrayStackNames(mFluidInputs));
+ result.put("Output " + ItemUtils.getArrayStackNames(mFluidOutputs));
+ result.put("Duration: " + mDuration);
+ result.put("EU/t: " + mEUt);
+ String s[] = result.toArray();
+ return s;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GasSpargingRecipeMap.java b/src/main/java/gregtech/api/util/GasSpargingRecipeMap.java
new file mode 100644
index 0000000000..6dcc7721e0
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GasSpargingRecipeMap.java
@@ -0,0 +1,45 @@
+package gregtech.api.util;
+
+import static gregtech.api.enums.Mods.GregTech;
+
+import net.minecraftforge.fluids.FluidStack;
+
+import gtPlusPlus.api.objects.data.AutoMap;
+
+public class GasSpargingRecipeMap extends AutoMap<GasSpargingRecipe> {
+
+ public static final AutoMap<GasSpargingRecipe> mRecipes = new AutoMap<>();
+ public static final String mUnlocalizedName = "gtpp.recipe.lftr.sparging";
+ public static final String mNEIName = mUnlocalizedName;
+ public static final String mNEIDisplayName = "LFTR Gas Sparging";
+ public static final String mNEIGUIPath = GregTech.getResourcePath("textures", "gui/basicmachines/FissionFuel.png");
+
+ public static boolean addRecipe(FluidStack aSpargeGas, FluidStack aSpentFuel, FluidStack aSpargedFuel,
+ FluidStack[] aOutputs, int[] aMaxOutputs) {
+ if (aSpargeGas == null || aSpargeGas.amount <= 0
+ || aSpentFuel == null
+ || aSpentFuel.amount <= 0
+ || aSpargedFuel == null
+ || aSpargedFuel.amount <= 0
+ || aOutputs == null
+ || aOutputs.length < 1
+ || aMaxOutputs == null
+ || aMaxOutputs.length < 1
+ || aOutputs.length != aMaxOutputs.length) {
+ return false;
+ }
+ int aMapSize = mRecipes.size();
+ GasSpargingRecipe aRecipe = new GasSpargingRecipe(aSpargeGas, aSpentFuel, aSpargedFuel, aOutputs, aMaxOutputs);
+ mRecipes.put(aRecipe);
+ return mRecipes.size() > aMapSize;
+ }
+
+ public static GasSpargingRecipe findRecipe(FluidStack aSpargeGas, FluidStack aSpentFuel) {
+ for (GasSpargingRecipe aRecipe : mRecipes) {
+ if (aRecipe.containsInputs(aSpargeGas, aSpentFuel)) {
+ return aRecipe;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/HotFuel.java b/src/main/java/gregtech/api/util/HotFuel.java
new file mode 100644
index 0000000000..6054a57b84
--- /dev/null
+++ b/src/main/java/gregtech/api/util/HotFuel.java
@@ -0,0 +1,40 @@
+package gregtech.api.util;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import gtPlusPlus.api.recipe.GTPPRecipeMaps;
+
+public class HotFuel {
+
+ public static void addNewHotFuel(FluidStack aInput1, FluidStack aOutput1, ItemStack[] outputItems, int[] chances,
+ int aSpecialValue) {
+ GTPPRecipeMaps.thermalBoilerRecipes.addRecipe(
+ true,
+ null,
+ outputItems,
+ null,
+ chances,
+ new FluidStack[] { aInput1 },
+ new FluidStack[] { aOutput1 },
+ 1, // 1 Tick
+ 0, // No Eu produced
+ aSpecialValue // Magic Number
+ );
+ }
+
+ public static void addNewHotFuel(FluidStack aInput1, FluidStack aOutput1, FluidStack aOutput2, int aSpecialValue) {
+ GTPPRecipeMaps.thermalBoilerRecipes.addRecipe(
+ false,
+ null,
+ null,
+ null,
+ null,
+ new FluidStack[] { aInput1 },
+ new FluidStack[] { aOutput1, aOutput2 },
+ 20, // 1 Second
+ 0, // No Eu produced
+ aSpecialValue // Magic Number
+ );
+ }
+}
diff --git a/src/main/java/gregtech/api/util/IGT_HatchAdder.java b/src/main/java/gregtech/api/util/IGT_HatchAdder.java
new file mode 100644
index 0000000000..21796f172e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/IGT_HatchAdder.java
@@ -0,0 +1,28 @@
+package gregtech.api.util;
+
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+
+public interface IGT_HatchAdder<T> {
+
+ /**
+ * Callback to add hatch, needs to check if hatch is valid (and add it)
+ *
+ * @param iGregTechTileEntity hatch
+ * @param aShort requested texture index, or null if not...
+ * @return managed to add hatch (structure still valid)
+ */
+ boolean apply(T t, IGregTechTileEntity iGregTechTileEntity, Short aShort);
+
+ /**
+ * hack to work around java generic issues.
+ */
+ @SuppressWarnings("unchecked")
+ default <T2 extends T> IGT_HatchAdder<T2> rebrand() {
+ return (IGT_HatchAdder<T2>) this;
+ }
+
+ default IGT_HatchAdder<T> orElse(IGT_HatchAdder<? super T> fallback) {
+ return (t, iGregTechTileEntity, aShort) -> IGT_HatchAdder.this.apply(t, iGregTechTileEntity, aShort)
+ || fallback.apply(t, iGregTechTileEntity, aShort);
+ }
+}
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..7f1626bac5
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ISerializableObject.java
@@ -0,0 +1,159 @@
+package gregtech.api.util;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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 com.google.common.io.ByteArrayDataInput;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 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();
+
+ /**
+ * If you are overriding this, you must <b>NOT</b> return {@link NBTTagInt} here! That return type is how we tell
+ * that we are loading legacy data, and only {@link LegacyCoverData} is allowed to return it. You probably want to
+ * return {@link NBTTagCompound} anyway.
+ */
+ @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.
+ *
+ * @param aPlayer the player who is sending this packet to server. null if it's client reading data.
+ */
+ // 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, @Nullable 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 NBTTagInt ? ((NBTTagInt) 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);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/LightingHelper.java b/src/main/java/gregtech/api/util/LightingHelper.java
new file mode 100644
index 0000000000..ad2510e937
--- /dev/null
+++ b/src/main/java/gregtech/api/util/LightingHelper.java
@@ -0,0 +1,1434 @@
+/*
+ * LightingHelper - Derived and adapted from @Mineshopper / carpentersblocks Copyright (c) 2013-2021. This library is
+ * free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation version 2.1 of the License. This library is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of
+ * the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package gregtech.api.util;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.EntityRenderer;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+@SuppressWarnings("unused")
+@SideOnly(Side.CLIENT)
+public class LightingHelper {
+
+ public static final int NORMAL_BRIGHTNESS = 0xff00ff;
+ public static final int MAX_BRIGHTNESS = 0xf000f0;
+ public static final float NO_Z_FIGHT_OFFSET = 1.0F / 1024.0F;
+ protected static final float[] LIGHTNESS = { 0.5F, 1.0F, 0.8F, 0.8F, 0.6F, 0.6F };
+ private final RenderBlocks renderBlocks;
+ /**
+ * Brightness for side.
+ */
+ private int brightness;
+ /**
+ * Ambient occlusion values for all four corners of side.
+ */
+ private float aoTopLeft, aoBottomLeft, aoBottomRight, aoTopRight;
+
+ private boolean hasLightnessOverride;
+ private float lightnessOverride;
+ private boolean hasBrightnessOverride;
+ private int brightnessOverride;
+ private boolean hasColorOverride;
+ private int colorOverride = 0xffffff;
+
+ /**
+ * Class constructor specifying the {@link RenderBlocks}.
+ *
+ * @param renderBlocks the {@link RenderBlocks}
+ */
+ public LightingHelper(RenderBlocks renderBlocks) {
+ this.renderBlocks = renderBlocks;
+ if (renderBlocks.useInventoryTint) {
+ // Block will be rendered in an inventory, so it needs its lightness maxed
+ setLightnessOverride(1.0F);
+ }
+ }
+
+ /**
+ * Gets average brightness from two brightness values.
+ *
+ * @param brightnessA the first brightness value
+ * @param brightnessB the second brightness value
+ * @return the mixed brightness
+ */
+ public static int getAverageBrightness(int brightnessA, int brightnessB) {
+ int sectionA1 = brightnessA >> 16 & 0xff;
+ int sectionA2 = brightnessA & 255;
+
+ int sectionB1 = brightnessB >> 16 & 0xff;
+ int sectionB2 = brightnessB & 255;
+
+ int difference1 = (int) ((sectionA1 + sectionB1) / 2.0F);
+ int difference2 = (int) ((sectionA2 + sectionB2) / 2.0F);
+
+ return difference1 << 16 | difference2;
+ }
+
+ /**
+ * Gets rgb color from RGBA array.
+ *
+ * @param color the integer color
+ * @return a float array with rgb values
+ */
+ public static float[] getRGB(short[] color) {
+ float red = color[0] / 255.0F;
+ float green = color[1] / 255.0F;
+ float blue = color[2] / 255.0F;
+
+ return new float[] { red, green, blue };
+ }
+
+ /**
+ * Clears brightness override.
+ */
+ public void clearBrightnessOverride() {
+ hasBrightnessOverride = false;
+ }
+
+ /**
+ * Clears color override.
+ */
+ public void clearColorOverride() {
+ hasColorOverride = false;
+ }
+
+ /**
+ * Clears lightness override.
+ */
+ public void clearLightnessOverride() {
+ hasLightnessOverride = false;
+ }
+
+ /**
+ * @return the Ambient Occlusion for Bottom-Left corner
+ */
+ public float getAoBottomLeft() {
+ return aoBottomLeft;
+ }
+
+ /**
+ * @return the Ambient Occlusion for Bottom-Right corner
+ */
+ public float getAoBottomRight() {
+ return aoBottomRight;
+ }
+
+ /**
+ * @return the Ambient Occlusion for Top-Left corner
+ */
+ public float getAoTopLeft() {
+ return aoTopLeft;
+ }
+
+ /**
+ * @return the Ambient Occlusion for Top-Right corner
+ */
+ public float getAoTopRight() {
+ return aoTopRight;
+ }
+
+ /**
+ * Sets brightness override.
+ *
+ * @param brightness the brightness override
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setBrightnessOverride(int brightness) {
+ hasBrightnessOverride = true;
+ brightnessOverride = brightness;
+ return this;
+ }
+
+ public LightingHelper setColorOverride(short[] color) {
+ return setColorOverride(getColor(color));
+ }
+
+ /**
+ * Sets color override.
+ *
+ * @param color the color override
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setColorOverride(int color) {
+ hasColorOverride = true;
+ colorOverride = color;
+ return this;
+ }
+
+ /**
+ * Gets int color from RGBA array.
+ *
+ * @param rgba the short RGBA color array
+ * @return int color
+ */
+ public static int getColor(short[] rgba) {
+ return (rgba[2] & 0xff) | (rgba[1] & 0xff) << 8 | (rgba[0] & 0xff) << 16;
+ }
+
+ /**
+ * Sets lightness override.
+ *
+ * @param lightness the lightness override
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setLightnessOverride(float lightness) {
+ hasLightnessOverride = true;
+ lightnessOverride = lightness;
+ return this;
+ }
+
+ /**
+ * Sets up the color using lightness, brightness, and the primary color value (usually the dye color) for the side.
+ *
+ * @param side the side
+ * @param rgba the primary short[] RGBA color array
+ */
+ public void setupColor(ForgeDirection side, short[] rgba) {
+ setupColor(side, getColor(rgba));
+ }
+
+ /**
+ * Sets up the color using lightness, brightness, and the primary color value (usually the dye color) for the side.
+ *
+ * @param side the side
+ * @param hexColor the primary color
+ */
+ public void setupColor(ForgeDirection side, int hexColor) {
+ Tessellator tessellator = Tessellator.instance;
+ float lightness = hasLightnessOverride ? lightnessOverride : LIGHTNESS[side.ordinal()];
+ float[] rgb = getRGB(hexColor);
+
+ if (hasColorOverride && !renderBlocks.hasOverrideBlockTexture()) {
+ rgb = getRGB(colorOverride);
+ }
+
+ applyAnaglyph(rgb);
+
+ if (renderBlocks.enableAO) {
+ tessellator.setBrightness(hasBrightnessOverride ? brightnessOverride : brightness);
+
+ if (renderBlocks.hasOverrideBlockTexture()) {
+
+ renderBlocks.colorRedTopLeft = renderBlocks.colorRedBottomLeft = renderBlocks.colorRedBottomRight = renderBlocks.colorRedTopRight = rgb[0];
+ renderBlocks.colorGreenTopLeft = renderBlocks.colorGreenBottomLeft = renderBlocks.colorGreenBottomRight = renderBlocks.colorGreenTopRight = rgb[1];
+ renderBlocks.colorBlueTopLeft = renderBlocks.colorBlueBottomLeft = renderBlocks.colorBlueBottomRight = renderBlocks.colorBlueTopRight = rgb[2];
+
+ } else {
+
+ renderBlocks.colorRedTopLeft = renderBlocks.colorRedBottomLeft = renderBlocks.colorRedBottomRight = renderBlocks.colorRedTopRight = rgb[0]
+ * lightness;
+ renderBlocks.colorGreenTopLeft = renderBlocks.colorGreenBottomLeft = renderBlocks.colorGreenBottomRight = renderBlocks.colorGreenTopRight = rgb[1]
+ * lightness;
+ renderBlocks.colorBlueTopLeft = renderBlocks.colorBlueBottomLeft = renderBlocks.colorBlueBottomRight = renderBlocks.colorBlueTopRight = rgb[2]
+ * lightness;
+
+ renderBlocks.colorRedTopLeft *= aoTopLeft;
+ renderBlocks.colorGreenTopLeft *= aoTopLeft;
+ renderBlocks.colorBlueTopLeft *= aoTopLeft;
+ renderBlocks.colorRedBottomLeft *= aoBottomLeft;
+ renderBlocks.colorGreenBottomLeft *= aoBottomLeft;
+ renderBlocks.colorBlueBottomLeft *= aoBottomLeft;
+ renderBlocks.colorRedBottomRight *= aoBottomRight;
+ renderBlocks.colorGreenBottomRight *= aoBottomRight;
+ renderBlocks.colorBlueBottomRight *= aoBottomRight;
+ renderBlocks.colorRedTopRight *= aoTopRight;
+ renderBlocks.colorGreenTopRight *= aoTopRight;
+ renderBlocks.colorBlueTopRight *= aoTopRight;
+ }
+
+ } else {
+
+ if (hasBrightnessOverride) tessellator.setBrightness(brightnessOverride);
+ tessellator.setColorOpaque_F(rgb[0] * lightness, rgb[1] * lightness, rgb[2] * lightness);
+ }
+ }
+
+ /**
+ * Gets rgb color from integer.
+ *
+ * @param color the integer color
+ * @return a float array with rgb values
+ */
+ public static float[] getRGB(int color) {
+ float red = (color >> 16 & 0xff) / 255.0F;
+ float green = (color >> 8 & 0xff) / 255.0F;
+ float blue = (color & 0xff) / 255.0F;
+
+ return new float[] { red, green, blue };
+ }
+
+ /**
+ * Will apply anaglyph color multipliers to RGB float array.
+ * <p>
+ * If {@link EntityRenderer#anaglyphEnable} is false, will do nothing.
+ *
+ * @param rgb array containing red, green and blue float values
+ */
+ public void applyAnaglyph(float[] rgb) {
+ if (EntityRenderer.anaglyphEnable) {
+ rgb[0] = (rgb[0] * 30.0F + rgb[1] * 59.0F + rgb[2] * 11.0F) / 100.0F;
+ rgb[1] = (rgb[0] * 30.0F + rgb[1] * 70.0F) / 100.0F;
+ rgb[2] = (rgb[0] * 30.0F + rgb[2] * 70.0F) / 100.0F;
+ }
+ }
+
+ /**
+ * Gets mixed ambient occlusion value from two inputs, with a ratio applied to the final result.
+ *
+ * @param ao1 the first ambient occlusion value
+ * @param ao2 the second ambient occlusion value
+ * @param ratio the ratio for mixing
+ * @return the mixed red, green, blue float values
+ */
+ public static float getMixedAo(float ao1, float ao2, double ratio) {
+ float diff = (float) (Math.abs(ao1 - ao2) * (1.0F - ratio));
+
+ return ao1 > ao2 ? ao1 - diff : ao1 + diff;
+ }
+
+ /**
+ * @see #setupLightingXNeg(Block, int, int, int)
+ * @see #setupLightingYNeg(Block, int, int, int)
+ * @see #setupLightingZNeg(Block, int, int, int)
+ * @see #setupLightingXPos(Block, int, int, int)
+ * @see #setupLightingYPos(Block, int, int, int)
+ * @see #setupLightingZPos(Block, int, int, int)
+ */
+ public LightingHelper setupLighting(Block block, int x, int y, int z, ForgeDirection facing) {
+ return switch (facing) {
+ case DOWN -> setupLightingYNeg(block, x, y, z);
+ case UP -> setupLightingYPos(block, x, y, z);
+ case NORTH -> setupLightingZNeg(block, x, y, z);
+ case SOUTH -> setupLightingZPos(block, x, y, z);
+ case WEST -> setupLightingXNeg(block, x, y, z);
+ case EAST -> setupLightingXPos(block, x, y, z);
+ default -> throw new IllegalArgumentException("Unknown side: " + facing);
+ };
+ }
+
+ /**
+ * Sets up lighting for the West face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingXNeg(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int xOffset = renderBlocks.renderMinX > 0.0F + NO_Z_FIGHT_OFFSET ? x : x - 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z);
+ brightness = mixedBrightness;
+
+ float ratio = (float) (1.0F - renderBlocks.renderMinX);
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x - 1, y, z)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXYNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z);
+ renderBlocks.aoBrightnessXZNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z - 1);
+ renderBlocks.aoBrightnessXZNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z + 1);
+ renderBlocks.aoBrightnessXYNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z);
+ renderBlocks.aoBrightnessXYZNNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z - 1);
+ renderBlocks.aoBrightnessXYZNNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z + 1);
+ renderBlocks.aoBrightnessXYZNPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z - 1);
+ renderBlocks.aoBrightnessXYZNPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z + 1);
+ renderBlocks.aoLightValueScratchXYNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXZNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXZNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+
+ int brightnessMixedXYZNPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZNN,
+ renderBlocks.aoBrightnessXYZNPN,
+ renderBlocks.aoBrightnessXYNP,
+ mixedBrightness);
+ int brightnessMixedXYZNNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZNNN,
+ renderBlocks.aoBrightnessXYNN,
+ renderBlocks.aoBrightnessXZNN,
+ mixedBrightness);
+ int brightnessMixedXYZNNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYNN,
+ renderBlocks.aoBrightnessXYZNNP,
+ renderBlocks.aoBrightnessXZNP,
+ mixedBrightness);
+ int brightnessMixedXYZNPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZNP,
+ renderBlocks.aoBrightnessXYNP,
+ renderBlocks.aoBrightnessXYZNPP,
+ mixedBrightness);
+
+ float aoMixedXYZNPN = (renderBlocks.aoLightValueScratchXZNN + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZNPN
+ + renderBlocks.aoLightValueScratchXYNP) / 4.0F;
+ float aoMixedXYZNNN = (renderBlocks.aoLightValueScratchXYZNNN + renderBlocks.aoLightValueScratchXYNN
+ + renderBlocks.aoLightValueScratchXZNN
+ + aoLightValue) / 4.0F;
+ float aoMixedXYZNNP = (renderBlocks.aoLightValueScratchXYNN + renderBlocks.aoLightValueScratchXYZNNP
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchXZNP) / 4.0F;
+ float aoMixedXYZNPP = (aoLightValue + renderBlocks.aoLightValueScratchXZNP
+ + renderBlocks.aoLightValueScratchXYNP
+ + renderBlocks.aoLightValueScratchXYZNPP) / 4.0F;
+
+ aoTopLeft = (float) (aoMixedXYZNPP * renderBlocks.renderMaxY * renderBlocks.renderMaxZ
+ + aoMixedXYZNPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxZ);
+ aoBottomLeft = (float) (aoMixedXYZNPP * renderBlocks.renderMaxY * renderBlocks.renderMinZ
+ + aoMixedXYZNPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinZ);
+ aoBottomRight = (float) (aoMixedXYZNPP * renderBlocks.renderMinY * renderBlocks.renderMinZ
+ + aoMixedXYZNPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinZ);
+ aoTopRight = (float) (aoMixedXYZNPP * renderBlocks.renderMinY * renderBlocks.renderMaxZ
+ + aoMixedXYZNPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxZ);
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZNNN,
+ brightnessMixedXYZNNP,
+ renderBlocks.renderMaxY * renderBlocks.renderMaxZ,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxZ),
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxZ),
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxZ);
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZNNN,
+ brightnessMixedXYZNNP,
+ renderBlocks.renderMaxY * renderBlocks.renderMinZ,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinZ),
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinZ),
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinZ);
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZNNN,
+ brightnessMixedXYZNNP,
+ renderBlocks.renderMinY * renderBlocks.renderMinZ,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinZ),
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinZ),
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinZ);
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZNNN,
+ brightnessMixedXYZNNP,
+ renderBlocks.renderMinY * renderBlocks.renderMaxZ,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxZ),
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxZ),
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxZ);
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets up lighting for the East face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingXPos(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int xOffset = renderBlocks.renderMaxX < 1.0F - NO_Z_FIGHT_OFFSET ? x : x + 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z);
+ brightness = mixedBrightness;
+
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x + 1, y, z)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXYPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z);
+ renderBlocks.aoBrightnessXZPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z - 1);
+ renderBlocks.aoBrightnessXZPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y, z + 1);
+ renderBlocks.aoBrightnessXYPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z);
+ renderBlocks.aoBrightnessXYZPNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z - 1);
+ renderBlocks.aoBrightnessXYZPNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y - 1, z + 1);
+ renderBlocks.aoBrightnessXYZPPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z - 1);
+ renderBlocks.aoBrightnessXYZPPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, xOffset, y + 1, z + 1);
+ renderBlocks.aoLightValueScratchXYPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXZPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXZPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXYPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXYZPNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXYZPNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXYZPPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+ renderBlocks.aoLightValueScratchXYZPPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxX);
+
+ int brightnessMixedXYZPPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZPP,
+ renderBlocks.aoBrightnessXYPP,
+ renderBlocks.aoBrightnessXYZPPP,
+ mixedBrightness);
+ int brightnessMixedXYZPNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYPN,
+ renderBlocks.aoBrightnessXYZPNP,
+ renderBlocks.aoBrightnessXZPP,
+ mixedBrightness);
+ int brightnessMixedXYZPNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZPNN,
+ renderBlocks.aoBrightnessXYPN,
+ renderBlocks.aoBrightnessXZPN,
+ mixedBrightness);
+ int brightnessMixedXYZPPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZPN,
+ renderBlocks.aoBrightnessXYZPPN,
+ renderBlocks.aoBrightnessXYPP,
+ mixedBrightness);
+
+ float aoMixedXYZPPP = (aoLightValue + renderBlocks.aoLightValueScratchXZPP
+ + renderBlocks.aoLightValueScratchXYPP
+ + renderBlocks.aoLightValueScratchXYZPPP) / 4.0F;
+ float aoMixedXYZPNP = (renderBlocks.aoLightValueScratchXYPN + renderBlocks.aoLightValueScratchXYZPNP
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchXZPP) / 4.0F;
+ float aoMixedXYZPNN = (renderBlocks.aoLightValueScratchXYZPNN + renderBlocks.aoLightValueScratchXYPN
+ + renderBlocks.aoLightValueScratchXZPN
+ + aoLightValue) / 4.0F;
+ float aoMixedXYZPPN = (renderBlocks.aoLightValueScratchXZPN + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZPPN
+ + renderBlocks.aoLightValueScratchXYPP) / 4.0F;
+
+ aoTopLeft = (float) (aoMixedXYZPNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxZ
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZPPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZPPP * renderBlocks.renderMinY * renderBlocks.renderMaxZ);
+ aoBottomLeft = (float) (aoMixedXYZPNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinZ
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZPPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZPPP * renderBlocks.renderMinY * renderBlocks.renderMinZ);
+ aoBottomRight = (float) (aoMixedXYZPNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinZ
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZPPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinZ)
+ + aoMixedXYZPPP * renderBlocks.renderMaxY * renderBlocks.renderMinZ);
+ aoTopRight = (float) (aoMixedXYZPNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxZ
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZPPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxZ)
+ + aoMixedXYZPPP * renderBlocks.renderMaxY * renderBlocks.renderMaxZ);
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPPP,
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxZ,
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxZ),
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxZ),
+ renderBlocks.renderMinY * renderBlocks.renderMaxZ);
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPPP,
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinZ,
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinZ),
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinZ),
+ renderBlocks.renderMinY * renderBlocks.renderMinZ);
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPPP,
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinZ,
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinZ),
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinZ),
+ renderBlocks.renderMaxY * renderBlocks.renderMinZ);
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPPP,
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxZ,
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxZ),
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxZ),
+ renderBlocks.renderMaxY * renderBlocks.renderMaxZ);
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets up lighting for the bottom face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingYNeg(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int yOffset = renderBlocks.renderMinY > 0.0F + NO_Z_FIGHT_OFFSET ? y : y - 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z);
+ brightness = mixedBrightness;
+
+ float ratio = (float) (1.0F - renderBlocks.renderMinY);
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x, y - 1, z)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXYNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z);
+ renderBlocks.aoBrightnessYZNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z - 1);
+ renderBlocks.aoBrightnessYZNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z + 1);
+ renderBlocks.aoBrightnessXYPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z);
+ renderBlocks.aoBrightnessXYZNNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z - 1);
+ renderBlocks.aoBrightnessXYZNNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z + 1);
+ renderBlocks.aoBrightnessXYZPNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z - 1);
+ renderBlocks.aoBrightnessXYZPNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z + 1);
+ renderBlocks.aoLightValueScratchXYNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchYZNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchYZNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZPNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZPNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+
+ int brightnessMixedXYZPNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZNP,
+ renderBlocks.aoBrightnessXYZPNP,
+ renderBlocks.aoBrightnessXYPN,
+ mixedBrightness);
+ int brightnessMixedXYZPNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZNN,
+ renderBlocks.aoBrightnessXYPN,
+ renderBlocks.aoBrightnessXYZPNN,
+ mixedBrightness);
+ int brightnessMixedXYZNNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYNN,
+ renderBlocks.aoBrightnessXYZNNN,
+ renderBlocks.aoBrightnessYZNN,
+ mixedBrightness);
+ int brightnessMixedXYZNNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZNNP,
+ renderBlocks.aoBrightnessXYNN,
+ renderBlocks.aoBrightnessYZNP,
+ mixedBrightness);
+
+ float aoMixedXYZPNP = (renderBlocks.aoLightValueScratchYZNP + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZPNP
+ + renderBlocks.aoLightValueScratchXYPN) / 4.0F;
+ float aoMixedXYZPNN = (aoLightValue + renderBlocks.aoLightValueScratchYZNN
+ + renderBlocks.aoLightValueScratchXYPN
+ + renderBlocks.aoLightValueScratchXYZPNN) / 4.0F;
+ float aoMixedXYZNNN = (renderBlocks.aoLightValueScratchXYNN + renderBlocks.aoLightValueScratchXYZNNN
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchYZNN) / 4.0F;
+ float aoMixedXYZNNP = (renderBlocks.aoLightValueScratchXYZNNP + renderBlocks.aoLightValueScratchXYNN
+ + renderBlocks.aoLightValueScratchYZNP
+ + aoLightValue) / 4.0F;
+
+ aoTopLeft = (float) (aoMixedXYZNNP * renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPNP * renderBlocks.renderMaxZ * renderBlocks.renderMinX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMinX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMinX));
+ aoBottomLeft = (float) (aoMixedXYZNNP * renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPNP * renderBlocks.renderMinZ * renderBlocks.renderMinX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMinX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMinX));
+ aoBottomRight = (float) (aoMixedXYZNNP * renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPNP * renderBlocks.renderMinZ * renderBlocks.renderMaxX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMaxX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMaxX));
+ aoTopRight = (float) (aoMixedXYZNNP * renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPNP * renderBlocks.renderMaxZ * renderBlocks.renderMaxX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMaxX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMaxX));
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMaxZ * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMinX));
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMinZ * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMinX));
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMinZ * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMaxX));
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMaxZ * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMaxX));
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets up lighting for the top face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingYPos(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int yOffset = renderBlocks.renderMaxY < 1.0F - NO_Z_FIGHT_OFFSET ? y : y + 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z);
+ brightness = mixedBrightness;
+
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x, y + 1, z)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXYNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z);
+ renderBlocks.aoBrightnessXYPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z);
+ renderBlocks.aoBrightnessYZPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z - 1);
+ renderBlocks.aoBrightnessYZPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, yOffset, z + 1);
+ renderBlocks.aoBrightnessXYZNPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z - 1);
+ renderBlocks.aoBrightnessXYZPPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z - 1);
+ renderBlocks.aoBrightnessXYZNPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, yOffset, z + 1);
+ renderBlocks.aoBrightnessXYZPPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, yOffset, z + 1);
+ renderBlocks.aoLightValueScratchXYNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchXYPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchYZPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchYZPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchXYZNPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchXYZPPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchXYZNPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+ renderBlocks.aoLightValueScratchXYZPPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxY);
+
+ int brightnessMixedXYZPPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZPP,
+ renderBlocks.aoBrightnessXYZPPP,
+ renderBlocks.aoBrightnessXYPP,
+ mixedBrightness);
+ int brightnessMixedXYZPPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZPN,
+ renderBlocks.aoBrightnessXYPP,
+ renderBlocks.aoBrightnessXYZPPN,
+ mixedBrightness);
+ int brightnessMixedXYZNPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYNP,
+ renderBlocks.aoBrightnessXYZNPN,
+ renderBlocks.aoBrightnessYZPN,
+ mixedBrightness);
+ int brightnessMixedXYZNPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZNPP,
+ renderBlocks.aoBrightnessXYNP,
+ renderBlocks.aoBrightnessYZPP,
+ mixedBrightness);
+
+ float aoMixedXYZPPP = (renderBlocks.aoLightValueScratchYZPP + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZPPP
+ + renderBlocks.aoLightValueScratchXYPP) / 4.0F;
+ float aoMixedXYZPPN = (aoLightValue + renderBlocks.aoLightValueScratchYZPN
+ + renderBlocks.aoLightValueScratchXYPP
+ + renderBlocks.aoLightValueScratchXYZPPN) / 4.0F;
+ float aoMixedXYZNPN = (renderBlocks.aoLightValueScratchXYNP + renderBlocks.aoLightValueScratchXYZNPN
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchYZPN) / 4.0F;
+ float aoMixedXYZNPP = (renderBlocks.aoLightValueScratchXYZNPP + renderBlocks.aoLightValueScratchXYNP
+ + renderBlocks.aoLightValueScratchYZPP
+ + aoLightValue) / 4.0F;
+
+ aoTopLeft /* SE */ = (float) (aoMixedXYZNPP * renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPP * renderBlocks.renderMaxZ * renderBlocks.renderMaxX
+ + aoMixedXYZPPN * (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMaxX
+ + aoMixedXYZNPN * (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMaxX));
+ aoBottomLeft /* NE */ = (float) (aoMixedXYZNPP * renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPP * renderBlocks.renderMinZ * renderBlocks.renderMaxX
+ + aoMixedXYZPPN * (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMaxX
+ + aoMixedXYZNPN * (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMaxX));
+ aoBottomRight /* NW */ = (float) (aoMixedXYZNPP * renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPP * renderBlocks.renderMinZ * renderBlocks.renderMinX
+ + aoMixedXYZPPN * (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMinX
+ + aoMixedXYZNPN * (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMinX));
+ aoTopRight /* SW */ = (float) (aoMixedXYZNPP * renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPP * renderBlocks.renderMaxZ * renderBlocks.renderMinX
+ + aoMixedXYZPPN * (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMinX
+ + aoMixedXYZNPN * (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMinX));
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZPPP,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZNPN,
+ renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMaxZ * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMaxX));
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZPPP,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZNPN,
+ renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMinZ * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMaxX));
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZPPP,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZNPN,
+ renderBlocks.renderMinZ * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMinZ * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinZ) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinZ) * (1.0D - renderBlocks.renderMinX));
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZPPP,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZNPN,
+ renderBlocks.renderMaxZ * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMaxZ * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxZ) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxZ) * (1.0D - renderBlocks.renderMinX));
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets up lighting for the North face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingZNeg(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int zOffset = renderBlocks.renderMinZ > 0.0F + NO_Z_FIGHT_OFFSET ? z : z - 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y, zOffset);
+ brightness = mixedBrightness;
+
+ float ratio = (float) (1.0F - renderBlocks.renderMinZ);
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x, y, z - 1)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXZNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y, zOffset);
+ renderBlocks.aoBrightnessYZNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y - 1, zOffset);
+ renderBlocks.aoBrightnessYZPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y + 1, zOffset);
+ renderBlocks.aoBrightnessXZPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y, zOffset);
+ renderBlocks.aoBrightnessXYZNNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y - 1, zOffset);
+ renderBlocks.aoBrightnessXYZNPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y + 1, zOffset);
+ renderBlocks.aoBrightnessXYZPNN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y - 1, zOffset);
+ renderBlocks.aoBrightnessXYZPPN = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y + 1, zOffset);
+ renderBlocks.aoLightValueScratchXZNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchYZNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchYZPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXZPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZNPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZPNN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+ renderBlocks.aoLightValueScratchXYZPPN = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z - 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ ratio);
+
+ int brightnessMixedXYZPPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZPN,
+ renderBlocks.aoBrightnessXZPN,
+ renderBlocks.aoBrightnessXYZPPN,
+ mixedBrightness);
+ int brightnessMixedXYZPNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZNN,
+ renderBlocks.aoBrightnessXYZPNN,
+ renderBlocks.aoBrightnessXZPN,
+ mixedBrightness);
+ int brightnessMixedXYZNNN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZNNN,
+ renderBlocks.aoBrightnessXZNN,
+ renderBlocks.aoBrightnessYZNN,
+ mixedBrightness);
+ int brightnessMixedXYZNPN = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZNN,
+ renderBlocks.aoBrightnessXYZNPN,
+ renderBlocks.aoBrightnessYZPN,
+ mixedBrightness);
+
+ float aoMixedXYZPPN = (aoLightValue + renderBlocks.aoLightValueScratchYZPN
+ + renderBlocks.aoLightValueScratchXZPN
+ + renderBlocks.aoLightValueScratchXYZPPN) / 4.0F;
+ float aoMixedXYZPNN = (renderBlocks.aoLightValueScratchYZNN + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZPNN
+ + renderBlocks.aoLightValueScratchXZPN) / 4.0F;
+ float aoMixedXYZNNN = (renderBlocks.aoLightValueScratchXYZNNN + renderBlocks.aoLightValueScratchXZNN
+ + renderBlocks.aoLightValueScratchYZNN
+ + aoLightValue) / 4.0F;
+ float aoMixedXYZNPN = (renderBlocks.aoLightValueScratchXZNN + renderBlocks.aoLightValueScratchXYZNPN
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchYZPN) / 4.0F;
+
+ aoTopLeft = (float) (aoMixedXYZNPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPN * renderBlocks.renderMaxY * renderBlocks.renderMinX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinX));
+ aoBottomLeft = (float) (aoMixedXYZNPN * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPN * renderBlocks.renderMaxY * renderBlocks.renderMaxX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxX));
+ aoBottomRight = (float) (aoMixedXYZNPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPN * renderBlocks.renderMinY * renderBlocks.renderMaxX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxX));
+ aoTopRight = (float) (aoMixedXYZNPN * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPN * renderBlocks.renderMinY * renderBlocks.renderMinX
+ + aoMixedXYZPNN * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinX
+ + aoMixedXYZNNN * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinX));
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMaxY * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinX));
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMaxY * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxX));
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxX),
+ renderBlocks.renderMinY * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxX,
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxX));
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPN,
+ brightnessMixedXYZPPN,
+ brightnessMixedXYZPNN,
+ brightnessMixedXYZNNN,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinX),
+ renderBlocks.renderMinY * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinX,
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinX));
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets up lighting for the South face and returns the {@link LightingHelper}.
+ * <p>
+ * This is a consolidated <code>method</code> that sets side shading with respect to the following attributes:
+ * <p>
+ * <ul>
+ * <li>{@link RenderBlocks#enableAO}</li>
+ * <li>{@link RenderBlocks#partialRenderBounds}</li>
+ * </ul>
+ *
+ * @param block the block {@link Block}
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @return the {@link LightingHelper}
+ */
+ public LightingHelper setupLightingZPos(Block block, int x, int y, int z) {
+
+ if (renderBlocks.enableAO) {
+
+ int zOffset = renderBlocks.renderMaxZ < 1.0F - NO_Z_FIGHT_OFFSET ? z : z + 1;
+
+ int mixedBrightness = block.getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y, zOffset);
+ brightness = mixedBrightness;
+
+ float aoLightValue = renderBlocks.blockAccess.getBlock(x, y, z + 1)
+ .getAmbientOcclusionLightValue();
+
+ renderBlocks.aoBrightnessXZNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y, zOffset);
+ renderBlocks.aoBrightnessXZPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y, zOffset);
+ renderBlocks.aoBrightnessYZNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y - 1, zOffset);
+ renderBlocks.aoBrightnessYZPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x, y + 1, zOffset);
+ renderBlocks.aoBrightnessXYZNNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y - 1, zOffset);
+ renderBlocks.aoBrightnessXYZNPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x - 1, y + 1, zOffset);
+ renderBlocks.aoBrightnessXYZPNP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y - 1, zOffset);
+ renderBlocks.aoBrightnessXYZPPP = block
+ .getMixedBrightnessForBlock(renderBlocks.blockAccess, x + 1, y + 1, zOffset);
+ renderBlocks.aoLightValueScratchXZNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchXZPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchYZNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchYZPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchXYZNNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchXYZNPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x - 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchXYZPNP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y - 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+ renderBlocks.aoLightValueScratchXYZPPP = getMixedAo(
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z + 1)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.blockAccess.getBlock(x + 1, y + 1, z)
+ .getAmbientOcclusionLightValue(),
+ renderBlocks.renderMaxZ);
+
+ int brightnessMixedXYZNPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXZNP,
+ renderBlocks.aoBrightnessXYZNPP,
+ renderBlocks.aoBrightnessYZPP,
+ mixedBrightness);
+ int brightnessMixedXYZNNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessXYZNNP,
+ renderBlocks.aoBrightnessXZNP,
+ renderBlocks.aoBrightnessYZNP,
+ mixedBrightness);
+ int brightnessMixedXYZPNP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZNP,
+ renderBlocks.aoBrightnessXYZPNP,
+ renderBlocks.aoBrightnessXZPP,
+ mixedBrightness);
+ int brightnessMixedXYZPPP = renderBlocks.getAoBrightness(
+ renderBlocks.aoBrightnessYZPP,
+ renderBlocks.aoBrightnessXZPP,
+ renderBlocks.aoBrightnessXYZPPP,
+ mixedBrightness);
+
+ float aoMixedXYZNPP = (renderBlocks.aoLightValueScratchXZNP + renderBlocks.aoLightValueScratchXYZNPP
+ + aoLightValue
+ + renderBlocks.aoLightValueScratchYZPP) / 4.0F;
+ float aoMixedXYZNNP = (renderBlocks.aoLightValueScratchXYZNNP + renderBlocks.aoLightValueScratchXZNP
+ + renderBlocks.aoLightValueScratchYZNP
+ + aoLightValue) / 4.0F;
+ float aoMixedXYZPNP = (renderBlocks.aoLightValueScratchYZNP + aoLightValue
+ + renderBlocks.aoLightValueScratchXYZPNP
+ + renderBlocks.aoLightValueScratchXZPP) / 4.0F;
+ float aoMixedXYZPPP = (aoLightValue + renderBlocks.aoLightValueScratchYZPP
+ + renderBlocks.aoLightValueScratchXZPP
+ + renderBlocks.aoLightValueScratchXYZPPP) / 4.0F;
+
+ aoTopLeft = (float) (aoMixedXYZNPP * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPP * renderBlocks.renderMaxY * renderBlocks.renderMinX
+ + aoMixedXYZPNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinX
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinX));
+ aoBottomLeft = (float) (aoMixedXYZNPP * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinX)
+ + aoMixedXYZPPP * renderBlocks.renderMinY * renderBlocks.renderMinX
+ + aoMixedXYZPNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinX
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinX));
+ aoBottomRight = (float) (aoMixedXYZNPP * renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPP * renderBlocks.renderMinY * renderBlocks.renderMaxX
+ + aoMixedXYZPNP * (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxX
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxX));
+ aoTopRight = (float) (aoMixedXYZNPP * renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxX)
+ + aoMixedXYZPPP * renderBlocks.renderMaxY * renderBlocks.renderMaxX
+ + aoMixedXYZPNP * (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxX
+ + aoMixedXYZNNP * (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxX));
+
+ renderBlocks.brightnessTopLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPPP,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMinX),
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMinX),
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMinX,
+ renderBlocks.renderMaxY * renderBlocks.renderMinX);
+ renderBlocks.brightnessBottomLeft = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPPP,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMinX),
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMinX),
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMinX,
+ renderBlocks.renderMinY * renderBlocks.renderMinX);
+ renderBlocks.brightnessBottomRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPPP,
+ renderBlocks.renderMinY * (1.0D - renderBlocks.renderMaxX),
+ (1.0D - renderBlocks.renderMinY) * (1.0D - renderBlocks.renderMaxX),
+ (1.0D - renderBlocks.renderMinY) * renderBlocks.renderMaxX,
+ renderBlocks.renderMinY * renderBlocks.renderMaxX);
+ renderBlocks.brightnessTopRight = renderBlocks.mixAoBrightness(
+ brightnessMixedXYZNPP,
+ brightnessMixedXYZNNP,
+ brightnessMixedXYZPNP,
+ brightnessMixedXYZPPP,
+ renderBlocks.renderMaxY * (1.0D - renderBlocks.renderMaxX),
+ (1.0D - renderBlocks.renderMaxY) * (1.0D - renderBlocks.renderMaxX),
+ (1.0D - renderBlocks.renderMaxY) * renderBlocks.renderMaxX,
+ renderBlocks.renderMaxY * renderBlocks.renderMaxX);
+ }
+
+ return this;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/MethodsReturnNonnullByDefault.java b/src/main/java/gregtech/api/util/MethodsReturnNonnullByDefault.java
new file mode 100644
index 0000000000..2b2c310695
--- /dev/null
+++ b/src/main/java/gregtech/api/util/MethodsReturnNonnullByDefault.java
@@ -0,0 +1,22 @@
+package gregtech.api.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.Nonnull;
+import javax.annotation.meta.TypeQualifierDefault;
+
+/**
+ * This annotation can be applied to a package or class to indicate that
+ * the methods in that element are nonnull by default unless there is:
+ * <ul>
+ * <li>An explicit nullness annotation
+ * <li>The method overrides a method in a superclass (in which case the
+ * annotation of the corresponding method in the superclass applies)
+ * </ul>
+ */
+@Nonnull
+@TypeQualifierDefault({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MethodsReturnNonnullByDefault {}
diff --git a/src/main/java/gregtech/api/util/OutputHatchWrapper.java b/src/main/java/gregtech/api/util/OutputHatchWrapper.java
new file mode 100644
index 0000000000..b2e74d24cf
--- /dev/null
+++ b/src/main/java/gregtech/api/util/OutputHatchWrapper.java
@@ -0,0 +1,65 @@
+package gregtech.api.util;
+
+import java.util.function.Predicate;
+
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+
+import org.jetbrains.annotations.NotNull;
+
+import gregtech.api.interfaces.fluid.IFluidStore;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Output;
+
+/**
+ * Wrapper for output hatch to allow multiblocks to apply specific filter.
+ */
+public class OutputHatchWrapper implements IFluidStore {
+
+ private final GT_MetaTileEntity_Hatch_Output outputHatch;
+ private final Predicate<FluidStack> filter;
+
+ public OutputHatchWrapper(GT_MetaTileEntity_Hatch_Output outputHatch, Predicate<FluidStack> filter) {
+ this.outputHatch = outputHatch;
+ this.filter = filter;
+ }
+
+ @Override
+ public FluidStack getFluid() {
+ return outputHatch.getFluid();
+ }
+
+ @Override
+ public int getFluidAmount() {
+ return outputHatch.getFluidAmount();
+ }
+
+ @Override
+ public int getCapacity() {
+ return outputHatch.getCapacity();
+ }
+
+ @Override
+ public FluidTankInfo getInfo() {
+ return outputHatch.getInfo();
+ }
+
+ @Override
+ public int fill(FluidStack resource, boolean doFill) {
+ return outputHatch.fill(resource, doFill);
+ }
+
+ @Override
+ public FluidStack drain(int maxDrain, boolean doDrain) {
+ return outputHatch.drain(maxDrain, doDrain);
+ }
+
+ @Override
+ public boolean isEmptyAndAcceptsAnyFluid() {
+ return outputHatch.isEmptyAndAcceptsAnyFluid();
+ }
+
+ @Override
+ public boolean canStoreFluid(@NotNull FluidStack fluidStack) {
+ return outputHatch.canStoreFluid(fluidStack) && filter.test(fluidStack);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/SemiFluidFuelHandler.java b/src/main/java/gregtech/api/util/SemiFluidFuelHandler.java
new file mode 100644
index 0000000000..e3baa9ac90
--- /dev/null
+++ b/src/main/java/gregtech/api/util/SemiFluidFuelHandler.java
@@ -0,0 +1,136 @@
+package gregtech.api.util;
+
+import static gtPlusPlus.api.recipe.GTPPRecipeMaps.semiFluidFuels;
+
+import java.util.HashMap;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidContainerRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.recipe.RecipeMaps;
+import gtPlusPlus.api.objects.Logger;
+import gtPlusPlus.api.objects.data.Pair;
+import gtPlusPlus.core.util.minecraft.FluidUtils;
+
+public class SemiFluidFuelHandler {
+
+ public static boolean addSemiFluidFuel(ItemStack aFuelItem, int aFuelValue) {
+ FluidStack p = FluidContainerRegistry.getFluidForFilledItem(aFuelItem);
+ if (p != null && aFuelValue > 0) {
+ return addSemiFluidFuel(p, aFuelValue);
+ } else {
+ Logger.INFO("Fuel value for " + aFuelItem.getDisplayName() + " is <= 0, ignoring.");
+ }
+ return false;
+ }
+
+ public static boolean addSemiFluidFuel(FluidStack aFuel, int aFuelValue) {
+ FluidStack p = aFuel;
+ if (p != null && aFuelValue > 0) {
+ GT_Recipe aRecipe = new GT_Recipe(
+ true,
+ new ItemStack[] {},
+ new ItemStack[] {},
+ null,
+ new int[] {},
+ new FluidStack[] { p },
+ null,
+ 0,
+ 0,
+ aFuelValue);
+ if (aRecipe.mSpecialValue > 0) {
+ Logger.INFO(
+ "Added " + aRecipe.mFluidInputs[0].getLocalizedName()
+ + " to the Semi-Fluid Generator fuel map. Fuel Produces "
+ + (aRecipe.mSpecialValue * 1000)
+ + "EU per 1000L.");
+ semiFluidFuels.add(aRecipe);
+ return true;
+ }
+ } else {
+ Logger.INFO("Fuel value for " + p != null ? p.getLocalizedName() : "NULL Fluid" + " is <= 0, ignoring.");
+ }
+ return false;
+ }
+
+ public static boolean generateFuels() {
+ final FluidStack aCreosote = FluidUtils.getFluidStack("creosote", 1000);
+ final FluidStack aHeavyFuel = FluidUtils.getFluidStack("liquid_heavy_fuel", 1000);
+ final FluidStack aHeavyOil = FluidUtils.getFluidStack("liquid_heavy_oil", 1000);
+ final HashMap<Integer, Pair<FluidStack, Integer>> aFoundFluidsFromItems = new HashMap<>();
+ // Find Fluids From items
+ for (final GT_Recipe r : RecipeMaps.denseLiquidFuels.getAllRecipes()) {
+
+ GT_Recipe g = r.copy();
+
+ if (g != null && g.mEnabled && g.mInputs.length > 0 && g.mInputs[0] != null) {
+ for (ItemStack i : g.mInputs) {
+ FluidStack f = FluidContainerRegistry.getFluidForFilledItem(i);
+ if (f != null) {
+ Pair<FluidStack, Integer> aData = new Pair<>(f, g.mSpecialValue);
+ aFoundFluidsFromItems.put(aData.hashCode(), aData);
+ }
+ }
+ } else if (g != null && g.mEnabled && g.mFluidInputs.length > 0 && g.mFluidInputs[0] != null) {
+ boolean aContainsCreosote = false;
+ for (FluidStack f : g.mFluidInputs) {
+ if (f.isFluidEqual(aCreosote)) {
+ aContainsCreosote = true;
+ }
+ }
+ g.mSpecialValue *= aContainsCreosote ? 6 : 3;
+ Logger.INFO(
+ "Added " + g.mFluidInputs[0].getLocalizedName()
+ + " to the Semi-Fluid Generator fuel map. Fuel Produces "
+ + g.mSpecialValue
+ + "EU per 1000L.");
+ semiFluidFuels.add(g);
+ }
+ }
+ for (Pair<FluidStack, Integer> p : aFoundFluidsFromItems.values()) {
+ if (p != null) {
+ int aFuelValue = p.getValue();
+ if (p.getKey()
+ .isFluidEqual(aCreosote)) {
+ aFuelValue *= 6;
+ } else if (p.getKey()
+ .isFluidEqual(aHeavyFuel)
+ || p.getKey()
+ .isFluidEqual(aHeavyOil)) {
+ aFuelValue *= 1.5;
+ } else {
+ aFuelValue *= 2;
+ }
+
+ if (aFuelValue <= (128 * 3)) {
+ GT_Recipe aRecipe = new GT_Recipe(
+ true,
+ new ItemStack[] {},
+ new ItemStack[] {},
+ null,
+ new int[] {},
+ new FluidStack[] { p.getKey() },
+ null,
+ 0,
+ 0,
+ aFuelValue);
+ if (aRecipe.mSpecialValue > 0) {
+ Logger.INFO(
+ "Added " + aRecipe.mFluidInputs[0].getLocalizedName()
+ + " to the Semi-Fluid Generator fuel map. Fuel Produces "
+ + (aRecipe.mSpecialValue * 1000)
+ + "EU per 1000L.");
+ semiFluidFuels.add(aRecipe);
+ }
+ } else {
+ Logger.INFO(
+ "Boosted Fuel value for " + p.getKey()
+ .getLocalizedName() + " exceeds 512k, ignoring.");
+ }
+ }
+ }
+ return !semiFluidFuels.getAllRecipes()
+ .isEmpty();
+ }
+}
diff --git a/src/main/java/gregtech/api/util/ValidationResult.java b/src/main/java/gregtech/api/util/ValidationResult.java
new file mode 100644
index 0000000000..497dfe67e5
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ValidationResult.java
@@ -0,0 +1,24 @@
+package gregtech.api.util;
+
+public class ValidationResult<T> {
+
+ private final ValidationType type;
+ private final T result;
+
+ private ValidationResult(ValidationType type, T result) {
+ this.type = type;
+ this.result = result;
+ }
+
+ public ValidationType getType() {
+ return this.type;
+ }
+
+ public T getResult() {
+ return this.result;
+ }
+
+ public static <T> ValidationResult<T> of(ValidationType result, T value) {
+ return new ValidationResult<>(result, value);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/ValidationType.java b/src/main/java/gregtech/api/util/ValidationType.java
new file mode 100644
index 0000000000..4417834430
--- /dev/null
+++ b/src/main/java/gregtech/api/util/ValidationType.java
@@ -0,0 +1,6 @@
+package gregtech.api.util;
+
+public enum ValidationType {
+ VALID,
+ INVALID
+}
diff --git a/src/main/java/gregtech/api/util/VoidProtectionHelper.java b/src/main/java/gregtech/api/util/VoidProtectionHelper.java
new file mode 100644
index 0000000000..fdf47d06df
--- /dev/null
+++ b/src/main/java/gregtech/api/util/VoidProtectionHelper.java
@@ -0,0 +1,485 @@
+package gregtech.api.util;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.function.Function;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
+import com.gtnewhorizons.modularui.api.fluids.IFluidTankLong;
+
+import gregtech.api.interfaces.fluid.IFluidStore;
+import gregtech.api.interfaces.tileentity.IVoidable;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+
+/**
+ * Helper class to calculate how many parallels of items / fluids can fit in the output buses / hatches.
+ */
+public class VoidProtectionHelper {
+
+ /**
+ * Machine used for calculation
+ */
+ private IVoidable machine;
+ /**
+ * Does void protection enabled for items
+ */
+ private boolean protectExcessItem;
+ /**
+ * Does void protection enabled for fluids
+ */
+ private boolean protectExcessFluid;
+ /**
+ * The maximum possible parallel possible for the multiblock
+ */
+ private int maxParallel = 1;
+ /**
+ * If item output is full.
+ */
+ private boolean isItemFull;
+ /**
+ * If fluid output is full.
+ */
+ private boolean isFluidFull;
+ /**
+ * The item outputs to check
+ */
+ private ItemStack[] itemOutputs;
+ /**
+ * The fluid outputs to check
+ */
+ private FluidStack[] fluidOutputs;
+ /**
+ * The item output inventory
+ */
+ private ItemInventoryLogic itemOutputInventory;
+ /**
+ * The fluid output inventory
+ */
+ private FluidInventoryLogic fluidOutputInventory;
+ /**
+ * Has this helper been built?
+ */
+ private boolean built;
+ /**
+ * Is this helper working for a MuTE?
+ */
+ private boolean muteMode;
+ /**
+ * Multiplier by which the output will be multiplied
+ */
+ private int outputMultiplier = 1;
+ /**
+ * Multiplier that is applied on the output chances
+ */
+ private double chanceMultiplier = 1;
+
+ private Function<Integer, Integer> chanceGetter = i -> 10000;
+
+ public VoidProtectionHelper() {}
+
+ /**
+ * Sets machine, with current configuration for void protection mode.
+ */
+ public VoidProtectionHelper setMachine(IVoidable machine) {
+ return setMachine(machine, machine.protectsExcessItem(), machine.protectsExcessFluid());
+ }
+
+ /**
+ * Sets machine, with void protection mode forcibly.
+ */
+ public VoidProtectionHelper setMachine(IVoidable machine, boolean protectExcessItem, boolean protectExcessFluid) {
+ this.protectExcessItem = protectExcessItem;
+ this.protectExcessFluid = protectExcessFluid;
+ this.machine = machine;
+ return this;
+ }
+
+ public VoidProtectionHelper setItemOutputs(ItemStack[] itemOutputs) {
+ this.itemOutputs = itemOutputs;
+ return this;
+ }
+
+ public VoidProtectionHelper setFluidOutputs(FluidStack[] fluidOutputs) {
+ this.fluidOutputs = fluidOutputs;
+ return this;
+ }
+
+ /**
+ * Sets the MaxParallel a multi can handle
+ */
+ public VoidProtectionHelper setMaxParallel(int maxParallel) {
+ this.maxParallel = maxParallel;
+ return this;
+ }
+
+ public VoidProtectionHelper setItemOutputInventory(ItemInventoryLogic itemOutputInventory) {
+ this.itemOutputInventory = itemOutputInventory;
+ return this;
+ }
+
+ public VoidProtectionHelper setFluidOutputInventory(FluidInventoryLogic fluidOutputInventory) {
+ this.fluidOutputInventory = fluidOutputInventory;
+ return this;
+ }
+
+ public VoidProtectionHelper setMuTEMode(boolean muteMode) {
+ this.muteMode = muteMode;
+ return this;
+ }
+
+ public VoidProtectionHelper setOutputMultiplier(int outputMultiplier) {
+ this.outputMultiplier = outputMultiplier;
+ return this;
+ }
+
+ public VoidProtectionHelper setChanceMultiplier(double chanceMultiplier) {
+ this.chanceMultiplier = chanceMultiplier;
+ return this;
+ }
+
+ public VoidProtectionHelper setChangeGetter(Function<Integer, Integer> getter) {
+ this.chanceGetter = getter;
+ return this;
+ }
+
+ /**
+ * Finishes the VoidProtectionHelper. Anything changed after this will not affect anything
+ */
+ public VoidProtectionHelper build() {
+ if (built) {
+ throw new IllegalStateException("Tried to build twice");
+ }
+ if (machine == null) {
+ throw new IllegalStateException("Machine is not set");
+ }
+ built = true;
+ determineParallel();
+ return this;
+ }
+
+ /**
+ * @return The current parallels possible by the multiblock
+ */
+ public int getMaxParallel() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get parallels before building");
+ }
+ return maxParallel;
+ }
+
+ /**
+ * @return If the calculation resulted in item output being full.
+ */
+ public boolean isItemFull() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get isItemFull before building");
+ }
+ return isItemFull;
+ }
+
+ /**
+ * @return If the calculation resulted in fluid output being full.
+ */
+ public boolean isFluidFull() {
+ if (!built) {
+ throw new IllegalStateException("Tried to get isFluidFull before building");
+ }
+ return isFluidFull;
+ }
+
+ /**
+ * Called by {@link #build()}. Determines the parallels and everything else that needs to be done at build time
+ */
+ private void determineParallel() {
+ if (itemOutputs == null) {
+ itemOutputs = new ItemStack[0];
+ }
+ if (fluidOutputs == null) {
+ fluidOutputs = new FluidStack[0];
+ }
+
+ // Don't check IVoidable#protectsExcessItem nor #protectsExcessFluid here,
+ // to allow more involved setting for void protections (see ComplexParallelProcessingLogic)
+ if (protectExcessItem && itemOutputs.length > 0 && !machine.canDumpItemToME()) {
+ maxParallel = Math.min(calculateMaxItemParallels(), maxParallel);
+ if (maxParallel <= 0) {
+ isItemFull = true;
+ return;
+ }
+ }
+ if (protectExcessFluid && fluidOutputs.length > 0 && !machine.canDumpFluidToME()) {
+ maxParallel = Math.min(calculateMaxFluidParallels(), maxParallel);
+ if (maxParallel <= 0) {
+ isFluidFull = true;
+ }
+ }
+ }
+
+ /**
+ * Calculates the max parallel for fluids if void protection is turned on
+ */
+ private int calculateMaxFluidParallels() {
+ List<? extends IFluidStore> hatches = machine.getFluidOutputSlots(fluidOutputs);
+ if (hatches.size() < fluidOutputs.length) {
+ return 0;
+ }
+
+ // A map to hold the items we will be 'inputting' into the output hatches. These fluidstacks are actually
+ // the recipe outputs.
+ Map<FluidStack, Integer> tFluidOutputMap = new HashMap<>();
+
+ // Map that keeps track of the number of parallel crafts we can accommodate for each fluid output.
+ // In the pair, we keep track of number of full crafts plus mb of fluid in a partial craft, to avoid
+ // issues with floating point math not being completely accurate when summing.
+ Map<FluidStack, ParallelData> tParallels = new HashMap<>();
+
+ // Iterate over the outputs, calculating require stack spacing they will require.
+ for (FluidStack aY : fluidOutputs) {
+ if (aY == null || aY.amount <= 0) {
+ continue;
+ }
+ tFluidOutputMap.merge(aY, aY.amount, Integer::sum);
+ tParallels.put(aY, new ParallelData(0, 0));
+ }
+
+ if (tFluidOutputMap.isEmpty()) {
+ // nothing to output, bail early
+ return maxParallel;
+ }
+
+ for (IFluidStore tHatch : hatches) {
+ int tSpaceLeft = tHatch.getCapacity() - tHatch.getFluidAmount();
+
+ // check if hatch filled
+ if (tSpaceLeft <= 0) continue;
+
+ // check if hatch is empty and unrestricted
+ if (tHatch.isEmptyAndAcceptsAnyFluid()) continue;
+
+ for (Map.Entry<FluidStack, ParallelData> entry : tParallels.entrySet()) {
+ FluidStack tFluidOutput = entry.getKey();
+ if (!tHatch.canStoreFluid(tFluidOutput)) continue;
+ // this fluid is not prevented by restrictions on output hatch
+ ParallelData tParallel = entry.getValue();
+ Integer tCraftSize = tFluidOutputMap.get(tFluidOutput);
+ tParallel.batch += (tParallel.partial + tSpaceLeft) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tSpaceLeft) % tCraftSize;
+ }
+ }
+ // now that all partial/restricted hatches have been counted, create a priority queue for our outputs
+ // the lowest priority fluid is the number of complete parallel crafts we can support
+ PriorityQueue<ParallelStackInfo<FluidStack>> aParallelQueue = new PriorityQueue<>(
+ Comparator.comparing(i -> i.batch));
+ for (Map.Entry<FluidStack, ParallelData> entry : tParallels.entrySet()) {
+ aParallelQueue
+ .add(new ParallelStackInfo<>(entry.getValue().batch, entry.getValue().partial, entry.getKey()));
+ }
+ // add extra parallels for open slots as well
+ for (IFluidStore tHatch : hatches) {
+ // partially filled or restricted hatch. done in the last pass
+ if (!tHatch.isEmptyAndAcceptsAnyFluid()) continue;
+
+ ParallelStackInfo<FluidStack> tParallel = aParallelQueue.poll();
+ assert tParallel != null; // will always be true, specifying assert here to avoid IDE/compiler warnings
+ Integer tCraftSize = tFluidOutputMap.get(tParallel.stack);
+ int tSpaceLeft = tHatch.getCapacity();
+ tParallel.batch += (tParallel.partial + tSpaceLeft) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tSpaceLeft) % tCraftSize;
+ aParallelQueue.add(tParallel);
+ }
+ return aParallelQueue.element().batch;
+ }
+
+ private int calculateMaxFluidParallelsMuTE() {
+ if (fluidOutputs.length > fluidOutputInventory.getInventory()
+ .getTanks()) {
+ return 0;
+ }
+
+ // A map to hold the items we will be 'inputting' into the output hatches. These fluidstacks are actually
+ // the recipe outputs.
+ Map<FluidStack, Integer> tFluidOutputMap = new HashMap<>();
+
+ // Map that keeps track of the number of parallel crafts we can accommodate for each fluid output.
+ // In the pair, we keep track of number of full crafts plus mb of fluid in a partial craft, to avoid
+ // issues with floating point math not being completely accurate when summing.
+ Map<FluidStack, ParallelData> tParallels = new HashMap<>();
+
+ // Iterate over the outputs, calculating require stack spacing they will require.
+ for (FluidStack aY : fluidOutputs) {
+ if (aY == null) continue;
+ int fluidAmount = aY.amount * outputMultiplier;
+ if (fluidAmount <= 0) continue;
+ tFluidOutputMap.merge(aY, fluidAmount, Integer::sum);
+ tParallels.put(aY, new ParallelData(0, 0));
+ }
+
+ if (tFluidOutputMap.isEmpty()) {
+ // nothing to output, bail early
+ return maxParallel;
+ }
+
+ for (int i = 0; i < fluidOutputInventory.getInventory()
+ .getTanks(); i++) {
+ IFluidTankLong tank = fluidOutputInventory.getInventory()
+ .getFluidTank(i);
+ long tSpaceLeft = tank.getCapacityLong() - tank.getFluidAmountLong();
+ // check if hatch filled
+ if (tSpaceLeft <= 0) continue;
+ // check if hatch is empty and unrestricted
+ if (tank.getStoredFluid() == null) continue;
+
+ for (Map.Entry<FluidStack, ParallelData> entry : tParallels.entrySet()) {
+ FluidStack tFluidOutput = entry.getKey();
+ if (tank.fill(tFluidOutput.getFluid(), tFluidOutput.amount, false) == tFluidOutput.amount) continue;
+ // this fluid is not prevented by restrictions on output hatch
+ ParallelData tParallel = entry.getValue();
+ Integer tCraftSize = tFluidOutputMap.get(tFluidOutput);
+ tParallel.batch += (tParallel.partial + tSpaceLeft) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tSpaceLeft) % tCraftSize;
+ }
+ }
+ // now that all partial/restricted hatches have been counted, create a priority queue for our outputs
+ // the lowest priority fluid is the number of complete parallel crafts we can support
+ PriorityQueue<ParallelStackInfo<FluidStack>> aParallelQueue = new PriorityQueue<>(
+ Comparator.comparing(i -> i.batch));
+ for (Map.Entry<FluidStack, ParallelData> entry : tParallels.entrySet()) {
+ aParallelQueue
+ .add(new ParallelStackInfo<>(entry.getValue().batch, entry.getValue().partial, entry.getKey()));
+ }
+ // add extra parallels for open slots as well
+ for (int i = 0; i < fluidOutputInventory.getInventory()
+ .getTanks(); i++) {
+ IFluidTankLong tank = fluidOutputInventory.getInventory()
+ .getFluidTank(i);
+ // partially filled or restricted hatch. done in the last pass
+ if (tank.getStoredFluid() != null) continue;
+
+ ParallelStackInfo<FluidStack> tParallel = aParallelQueue.poll();
+ assert tParallel != null; // will always be true, specifying assert here to avoid IDE/compiler warnings
+ Integer tCraftSize = tFluidOutputMap.get(tParallel.stack);
+ long tSpaceLeft = tank.getCapacityLong();
+ tParallel.batch += (tParallel.partial + tSpaceLeft) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tSpaceLeft) % tCraftSize;
+ aParallelQueue.add(tParallel);
+ }
+
+ return aParallelQueue.element().batch;
+ }
+
+ /**
+ * Calculates the max parallels one can do with items if void protection is on
+ */
+ private int calculateMaxItemParallels() {
+ List<ItemStack> busStacks;
+
+ if (muteMode) {
+ busStacks = itemOutputInventory.getInventory()
+ .getStacks();
+ } else {
+ busStacks = machine.getItemOutputSlots(itemOutputs);
+ }
+ // A map to hold the items we will be 'inputting' into the output buses. These itemstacks are actually the
+ // recipe outputs.
+ Map<ItemStack, Integer> tItemOutputMap = new ItemStackMap<>();
+
+ // Map that keeps track of the number of parallel crafts we can accommodate for each item output.
+ // In the pair, we keep track of number of full crafts plus number of items in a partial craft, to avoid
+ // issues with floating point math not being completely accurate when summing.
+ Map<ItemStack, ParallelData> tParallels = new ItemStackMap<>();
+ int tSlotsFree = 0;
+ int index = 0;
+ for (ItemStack tItem : itemOutputs) {
+ // GT_RecipeBuilder doesn't handle null item output
+ if (tItem == null) continue;
+ int itemStackSize = (int) (tItem.stackSize * outputMultiplier
+ * Math.ceil(chanceMultiplier * chanceGetter.apply(index++) / 10000));
+ if (itemStackSize <= 0) continue;
+ tItemOutputMap.merge(tItem, itemStackSize, Integer::sum);
+ tParallels.put(tItem, new ParallelData(0, 0));
+ }
+
+ if (tItemOutputMap.isEmpty()) {
+ // nothing to output, bail early
+ return maxParallel;
+ }
+
+ if (itemOutputs.length > 0) {
+ for (ItemStack tBusStack : busStacks) {
+ if (tBusStack == null) {
+ tSlotsFree++;
+ } else {
+ // get the real stack size
+ // we ignore the bus inventory stack limit here as no one set it to anything other than 64
+ int tMaxBusStackSize = tBusStack.getMaxStackSize();
+ if (tBusStack.stackSize >= tMaxBusStackSize)
+ // this bus stack is full. no checking
+ continue;
+ int tSpaceLeft = tMaxBusStackSize - tBusStack.stackSize;
+ Integer tCraftSize = tItemOutputMap.get(tBusStack);
+ if (tCraftSize == null) {
+ // we don't have a matching stack to output, ignore this bus stack
+ continue;
+ }
+ ParallelData tParallel = tParallels.get(tBusStack);
+ tParallel.batch += (tParallel.partial + tSpaceLeft) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tSpaceLeft) % tCraftSize;
+ }
+
+ }
+ // now that all partial stacks have been counted, create a priority queue for our outputs
+ // the lowest priority item is the number of complete parallel crafts we can support
+ PriorityQueue<ParallelStackInfo<ItemStack>> aParallelQueue = new PriorityQueue<>(
+ Comparator.comparing(i -> i.batch));
+ for (Map.Entry<ItemStack, ParallelData> entry : tParallels.entrySet()) {
+ aParallelQueue
+ .add(new ParallelStackInfo<>(entry.getValue().batch, entry.getValue().partial, entry.getKey()));
+ }
+
+ while (tSlotsFree > 0) {
+ ParallelStackInfo<ItemStack> tParallel = aParallelQueue.poll();
+ assert tParallel != null; // will always be true, specifying assert here to avoid IDE/compiler warnings
+ Integer tCraftSize = tItemOutputMap.get(tParallel.stack);
+ int tStackSize = tParallel.stack.getMaxStackSize();
+ tParallel.batch += (tParallel.partial + tStackSize) / tCraftSize;
+ tParallel.partial = (tParallel.partial + tStackSize) % tCraftSize;
+ aParallelQueue.add(tParallel);
+ --tSlotsFree;
+ }
+
+ return aParallelQueue.element().batch;
+ }
+ return 0;
+ }
+
+ private static class ParallelData {
+
+ private int batch;
+ private long partial;
+
+ private ParallelData(int batch, long partial) {
+ this.batch = batch;
+ this.partial = partial;
+ }
+ }
+
+ private static class ParallelStackInfo<T> {
+
+ private int batch;
+ private long partial;
+ private final T stack;
+
+ private ParallelStackInfo(int batch, long partial, T stack) {
+ this.batch = batch;
+ this.partial = partial;
+ this.stack = stack;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/WorldSpawnedEventBuilder.java b/src/main/java/gregtech/api/util/WorldSpawnedEventBuilder.java
new file mode 100644
index 0000000000..f2bcce2bf8
--- /dev/null
+++ b/src/main/java/gregtech/api/util/WorldSpawnedEventBuilder.java
@@ -0,0 +1,678 @@
+package gregtech.api.util;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.Vec3;
+import net.minecraft.world.World;
+
+@SuppressWarnings("unused")
+public abstract class WorldSpawnedEventBuilder implements Runnable {
+
+ private static final String ILLEGAL_STATE_STR1 = "Position, identifier and world must be set";
+ /* Variables */
+
+ private World world;
+
+ /* Getters, Setters */
+
+ public World getWorld() {
+ return world;
+ }
+
+ public WorldSpawnedEventBuilder setWorld(World world) {
+ this.world = world;
+ return this;
+ }
+
+ /* Methods */
+
+ @SuppressWarnings("unchecked")
+ public <U extends WorldSpawnedEventBuilder> void times(int times, Consumer<U> action) {
+ Objects.requireNonNull(action);
+ for (int i = 0; i < times; i++) {
+ action.accept((U) this);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <U extends WorldSpawnedEventBuilder> void times(int times, BiConsumer<U, Integer> action) {
+ Objects.requireNonNull(action);
+ for (int i = 0; i < times; i++) {
+ action.accept((U) this, i);
+ }
+ }
+
+ /* Interfaces */
+
+ private interface IPositionedWorldSpawnedEvent {
+
+ Vec3 getPosition();
+
+ IPositionedWorldSpawnedEvent setPosition(Vec3 position);
+
+ IPositionedWorldSpawnedEvent setPosition(double x, double y, double z);
+ }
+
+ private interface IEntityWorldSpawnedEvent {
+
+ Entity getEntity();
+
+ IEntityWorldSpawnedEvent setEntity(Entity entity);
+ }
+
+ private interface IEntityPlayerWorldSpawnedEvent {
+
+ EntityPlayer getEntityPlayer();
+
+ IEntityPlayerWorldSpawnedEvent setEntityPlayer(EntityPlayer entity);
+ }
+
+ private interface IStringIdentifierWorldSpawnedEvent {
+
+ String getIdentifier();
+
+ IStringIdentifierWorldSpawnedEvent setIdentifier(String identifier);
+
+ IStringIdentifierWorldSpawnedEvent setIdentifier(Enum<?> identifier);
+ }
+
+ private interface ISoundWorldSpawnedEvent {
+
+ float getPitch();
+
+ float getVolume();
+
+ ISoundWorldSpawnedEvent setPitch(float pitch);
+
+ ISoundWorldSpawnedEvent setVolume(float volume);
+ }
+
+ /* Abstract Classes */
+
+ private abstract static class EntityWorldSpawnedEventBuilder extends WorldSpawnedEventBuilder
+ implements IEntityWorldSpawnedEvent {
+
+ private Entity entity;
+
+ @Override
+ public Entity getEntity() {
+ return entity;
+ }
+
+ @Override
+ public EntityWorldSpawnedEventBuilder setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ private abstract static class PositionedEntityWorldSpawnedEventBuilder extends EntityWorldSpawnedEventBuilder
+ implements IPositionedWorldSpawnedEvent {
+
+ private Vec3 position;
+
+ @Override
+ public Vec3 getPosition() {
+ return position;
+ }
+
+ @Override
+ public PositionedEntityWorldSpawnedEventBuilder setPosition(Vec3 position) {
+ this.position = position;
+ return this;
+ }
+
+ @Override
+ public PositionedEntityWorldSpawnedEventBuilder setPosition(double x, double y, double z) {
+ this.position = Vec3.createVectorHelper(x, y, z);
+ return this;
+ }
+ }
+
+ private abstract static class PositionedWorldSpawnedEventBuilder extends WorldSpawnedEventBuilder
+ implements IPositionedWorldSpawnedEvent {
+
+ private Vec3 position;
+
+ @Override
+ public Vec3 getPosition() {
+ return position;
+ }
+
+ @Override
+ public PositionedWorldSpawnedEventBuilder setPosition(Vec3 position) {
+ this.position = position;
+ return this;
+ }
+
+ @Override
+ public PositionedWorldSpawnedEventBuilder setPosition(double x, double y, double z) {
+ this.position = Vec3.createVectorHelper(x, y, z);
+ return this;
+ }
+ }
+
+ private abstract static class StringIdentifierPositionedWorldSpawnedEventBuilder
+ extends PositionedWorldSpawnedEventBuilder implements IStringIdentifierWorldSpawnedEvent {
+
+ private String identifier;
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public StringIdentifierPositionedWorldSpawnedEventBuilder setIdentifier(String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ @Override
+ public StringIdentifierPositionedWorldSpawnedEventBuilder setIdentifier(Enum<?> identifier) {
+ this.identifier = identifier.toString();
+ return this;
+ }
+ }
+
+ private abstract static class SoundStringIdentifierPositionedWorldSpawnedEventBuilder
+ extends StringIdentifierPositionedWorldSpawnedEventBuilder implements ISoundWorldSpawnedEvent {
+
+ private float pitch;
+ private float volume;
+
+ @Override
+ public float getPitch() {
+ return pitch;
+ }
+
+ @Override
+ public float getVolume() {
+ return volume;
+ }
+
+ @Override
+ public SoundStringIdentifierPositionedWorldSpawnedEventBuilder setPitch(float pitch) {
+ this.pitch = pitch;
+ return this;
+ }
+
+ @Override
+ public SoundStringIdentifierPositionedWorldSpawnedEventBuilder setVolume(float volume) {
+ this.volume = volume;
+ return this;
+ }
+ }
+
+ /* Implementations */
+
+ public static final class ParticleEventBuilder extends StringIdentifierPositionedWorldSpawnedEventBuilder {
+
+ private Vec3 motion;
+
+ public Vec3 getMotion() {
+ return motion;
+ }
+
+ public ParticleEventBuilder setMotion(double x, double y, double z) {
+ this.motion = Vec3.createVectorHelper(x, y, z);
+ return this;
+ }
+
+ public ParticleEventBuilder setMotion(Vec3 motion) {
+ this.motion = motion;
+ return this;
+ }
+
+ @Override
+ public ParticleEventBuilder setWorld(World world) {
+ return (ParticleEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public ParticleEventBuilder setPosition(Vec3 position) {
+ return (ParticleEventBuilder) super.setPosition(position);
+ }
+
+ @Override
+ public ParticleEventBuilder setPosition(double x, double y, double z) {
+ return (ParticleEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public ParticleEventBuilder setIdentifier(String identifier) {
+ return (ParticleEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public ParticleEventBuilder setIdentifier(Enum<?> identifier) {
+ return (ParticleEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public void run() {
+ if (getPosition() == null || getIdentifier() == null || getMotion() == null || getWorld() == null)
+ throw new IllegalStateException("Position, identifier, motion and world must be set");
+
+ getWorld().spawnParticle(
+ getIdentifier(),
+ getPosition().xCoord,
+ getPosition().yCoord,
+ getPosition().zCoord,
+ getMotion().xCoord,
+ getMotion().yCoord,
+ getMotion().zCoord);
+ }
+ }
+
+ public static final class SoundEffectEventBuilder extends SoundStringIdentifierPositionedWorldSpawnedEventBuilder {
+
+ @Override
+ public SoundEffectEventBuilder setWorld(World world) {
+ return (SoundEffectEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setPosition(Vec3 position) {
+ return (SoundEffectEventBuilder) super.setPosition(position);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setPosition(double x, double y, double z) {
+ return (SoundEffectEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setIdentifier(String identifier) {
+ return (SoundEffectEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setIdentifier(Enum<?> identifier) {
+ return (SoundEffectEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setPitch(float pitch) {
+ return (SoundEffectEventBuilder) super.setPitch(pitch);
+ }
+
+ @Override
+ public SoundEffectEventBuilder setVolume(float volume) {
+ return (SoundEffectEventBuilder) super.setVolume(volume);
+ }
+
+ @Override
+ public void run() {
+ if (getPosition() == null || getIdentifier() == null || getWorld() == null)
+ throw new IllegalStateException(ILLEGAL_STATE_STR1);
+
+ getWorld().playSoundEffect(
+ getPosition().xCoord,
+ getPosition().yCoord,
+ getPosition().zCoord,
+ getIdentifier(),
+ getPitch(),
+ getVolume());
+ }
+ }
+
+ public static final class SoundEventBuilder extends SoundStringIdentifierPositionedWorldSpawnedEventBuilder {
+
+ private boolean proximity;
+
+ public boolean isProximity() {
+ return proximity;
+ }
+
+ @Override
+ public SoundEventBuilder setWorld(World world) {
+ return (SoundEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public SoundEventBuilder setPosition(Vec3 position) {
+ return (SoundEventBuilder) super.setPosition(position);
+ }
+
+ @Override
+ public SoundEventBuilder setPosition(double x, double y, double z) {
+ return (SoundEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public SoundEventBuilder setIdentifier(String identifier) {
+ return (SoundEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public SoundEventBuilder setPitch(float pitch) {
+ return (SoundEventBuilder) super.setPitch(pitch);
+ }
+
+ @Override
+ public SoundEventBuilder setVolume(float volume) {
+ return (SoundEventBuilder) super.setVolume(volume);
+ }
+
+ public SoundEventBuilder setProximity(boolean proximity) {
+ this.proximity = proximity;
+ return this;
+ }
+
+ @Override
+ public void run() {
+ if (getPosition() == null || getIdentifier() == null || getWorld() == null)
+ throw new IllegalStateException(ILLEGAL_STATE_STR1);
+
+ getWorld().playSound(
+ getPosition().xCoord,
+ getPosition().yCoord,
+ getPosition().zCoord,
+ getIdentifier(),
+ getPitch(),
+ getVolume(),
+ isProximity());
+ }
+ }
+
+ /**
+ * Positional Data is rounded down due to this targeting a block.
+ */
+ public static final class RecordEffectEventBuilder extends StringIdentifierPositionedWorldSpawnedEventBuilder {
+
+ @Override
+ public RecordEffectEventBuilder setWorld(World world) {
+ return (RecordEffectEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public RecordEffectEventBuilder setPosition(Vec3 position) {
+ return (RecordEffectEventBuilder) super.setPosition(position);
+ }
+
+ @Override
+ public RecordEffectEventBuilder setPosition(double x, double y, double z) {
+ return (RecordEffectEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public RecordEffectEventBuilder setIdentifier(String identifier) {
+ return (RecordEffectEventBuilder) super.setIdentifier(identifier);
+ }
+
+ @Override
+ public void run() {
+ if (getPosition() == null || getIdentifier() == null || getWorld() == null)
+ throw new IllegalStateException(ILLEGAL_STATE_STR1);
+
+ getWorld().playRecord(
+ getIdentifier(),
+ (int) getPosition().xCoord,
+ (int) getPosition().yCoord,
+ (int) getPosition().zCoord);
+ }
+ }
+
+ public static final class ExplosionEffectEventBuilder extends PositionedEntityWorldSpawnedEventBuilder {
+
+ private boolean isFlaming, isSmoking;
+ private float strength;
+
+ public float getStrength() {
+ return strength;
+ }
+
+ public ExplosionEffectEventBuilder setStrength(float strength) {
+ this.strength = strength;
+ return this;
+ }
+
+ public boolean isFlaming() {
+ return isFlaming;
+ }
+
+ public ExplosionEffectEventBuilder setFlaming(boolean flaming) {
+ isFlaming = flaming;
+ return this;
+ }
+
+ public boolean isSmoking() {
+ return isSmoking;
+ }
+
+ public ExplosionEffectEventBuilder setSmoking(boolean smoking) {
+ isSmoking = smoking;
+ return this;
+ }
+
+ @Override
+ public ExplosionEffectEventBuilder setWorld(World world) {
+ return (ExplosionEffectEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public ExplosionEffectEventBuilder setEntity(Entity entity) {
+ return (ExplosionEffectEventBuilder) super.setEntity(entity);
+ }
+
+ @Override
+ public ExplosionEffectEventBuilder setPosition(double x, double y, double z) {
+ return (ExplosionEffectEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public void run() {
+ if (getPosition() == null || getWorld() == null)
+ throw new IllegalStateException("Position and world must be set");
+
+ getWorld().newExplosion(
+ getEntity(),
+ getPosition().xCoord,
+ getPosition().yCoord,
+ getPosition().zCoord,
+ strength,
+ isFlaming,
+ isSmoking);
+ }
+ }
+
+ /**
+ * Positional Data is rounded down due to this targeting a block.
+ */
+ public static final class ExtinguishFireEffectEventBuilder extends PositionedWorldSpawnedEventBuilder
+ implements IEntityPlayerWorldSpawnedEvent {
+
+ private int side;
+ private EntityPlayer entityPlayer;
+
+ public int getSide() {
+ return side;
+ }
+
+ public ExtinguishFireEffectEventBuilder setSide(int side) {
+ this.side = side;
+ return this;
+ }
+
+ @Override
+ public EntityPlayer getEntityPlayer() {
+ return entityPlayer;
+ }
+
+ @Override
+ public ExtinguishFireEffectEventBuilder setEntityPlayer(EntityPlayer entity) {
+ this.entityPlayer = entity;
+ return this;
+ }
+
+ @Override
+ public ExtinguishFireEffectEventBuilder setWorld(World world) {
+ return (ExtinguishFireEffectEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public ExtinguishFireEffectEventBuilder setPosition(Vec3 position) {
+ return (ExtinguishFireEffectEventBuilder) super.setPosition(position);
+ }
+
+ @Override
+ public ExtinguishFireEffectEventBuilder setPosition(double x, double y, double z) {
+ return (ExtinguishFireEffectEventBuilder) super.setPosition(x, y, z);
+ }
+
+ @Override
+ public void run() {
+ if (getEntityPlayer() == null || getPosition() == null || getWorld() == null)
+ throw new IllegalStateException("EntityPlayer, position and world must be set");
+
+ getWorld().extinguishFire(
+ getEntityPlayer(),
+ (int) getPosition().xCoord,
+ (int) getPosition().yCoord,
+ (int) getPosition().zCoord,
+ side);
+ }
+ }
+
+ public static final class SoundAtEntityEventBuilder extends EntityWorldSpawnedEventBuilder
+ implements ISoundWorldSpawnedEvent, IStringIdentifierWorldSpawnedEvent {
+
+ private float pitch;
+ private float volume;
+ private String identifier;
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setIdentifier(String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setIdentifier(Enum<?> identifier) {
+ this.identifier = identifier.toString();
+ return this;
+ }
+
+ @Override
+ public float getPitch() {
+ return pitch;
+ }
+
+ @Override
+ public float getVolume() {
+ return volume;
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setPitch(float pitch) {
+ this.pitch = pitch;
+ return this;
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setVolume(float volume) {
+ this.volume = volume;
+ return this;
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setWorld(World world) {
+ return (SoundAtEntityEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public SoundAtEntityEventBuilder setEntity(Entity entity) {
+ return (SoundAtEntityEventBuilder) super.setEntity(entity);
+ }
+
+ @Override
+ public void run() {
+ if (getWorld() == null || getIdentifier() == null || getEntity() == null)
+ throw new IllegalStateException("World, Identifier and entity must be set!");
+
+ getWorld().playSoundAtEntity(getEntity(), getIdentifier(), volume, pitch);
+ }
+ }
+
+ public static final class SoundToNearExceptEventBuilder extends WorldSpawnedEventBuilder
+ implements ISoundWorldSpawnedEvent, IStringIdentifierWorldSpawnedEvent, IEntityPlayerWorldSpawnedEvent {
+
+ private float pitch;
+ private float volume;
+ private String identifier;
+ private EntityPlayer entityPlayer;
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setIdentifier(String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setIdentifier(Enum<?> identifier) {
+ this.identifier = identifier.toString();
+ return this;
+ }
+
+ @Override
+ public float getPitch() {
+ return pitch;
+ }
+
+ @Override
+ public float getVolume() {
+ return volume;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setPitch(float pitch) {
+ this.pitch = pitch;
+ return this;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setVolume(float volume) {
+ this.volume = volume;
+ return this;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setWorld(World world) {
+ return (SoundToNearExceptEventBuilder) super.setWorld(world);
+ }
+
+ @Override
+ public void run() {
+ if (getWorld() == null || getIdentifier() == null || getEntityPlayer() == null)
+ throw new IllegalStateException("World, Identifier and EntityPlayer must be set!");
+
+ getWorld().playSoundAtEntity(getEntityPlayer(), getIdentifier(), volume, pitch);
+ }
+
+ @Override
+ public EntityPlayer getEntityPlayer() {
+ return entityPlayer;
+ }
+
+ @Override
+ public SoundToNearExceptEventBuilder setEntityPlayer(EntityPlayer entity) {
+ entityPlayer = entity;
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/extensions/ArrayExt.java b/src/main/java/gregtech/api/util/extensions/ArrayExt.java
new file mode 100644
index 0000000000..b6ebb07d38
--- /dev/null
+++ b/src/main/java/gregtech/api/util/extensions/ArrayExt.java
@@ -0,0 +1,81 @@
+package gregtech.api.util.extensions;
+
+import java.util.function.IntFunction;
+
+public class ArrayExt {
+
+ public static int[] of(int... objects) {
+ return objects;
+ }
+
+ public static float[] of(float... objects) {
+ return objects;
+ }
+
+ public static double[] of(double... objects) {
+ return objects;
+ }
+
+ public static char[] of(char... objects) {
+ return objects;
+ }
+
+ public static byte[] of(byte... objects) {
+ return objects;
+ }
+
+ public static long[] of(long... objects) {
+ return objects;
+ }
+
+ @SafeVarargs
+ public static <T> T[] of(T... objects) {
+ return objects;
+ }
+
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ public static <T> T[] withoutNulls(T[] array, IntFunction<T[]> arrayFactory) {
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] != null) {
+ count++;
+ }
+ }
+
+ T[] newArr = arrayFactory.apply(count);
+ if (count == 0) return newArr;
+
+ int j = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] != null) {
+ newArr[j] = array[i];
+ j++;
+ }
+ }
+
+ return newArr;
+ }
+
+ public static <T> T[] withoutTrailingNulls(T[] array, IntFunction<T[]> arrayFactory) {
+ int firstNull = -1;
+ for (int i = array.length - 1; i >= 0; i--) {
+ if (array[i] == null) {
+ firstNull = i;
+ } else {
+ break;
+ }
+ }
+
+ if (firstNull == -1) {
+ T[] newArray = arrayFactory.apply(array.length);
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ return newArray;
+ } else if (firstNull == 0) {
+ return arrayFactory.apply(0);
+ } else {
+ T[] newArray = arrayFactory.apply(firstNull);
+ System.arraycopy(array, 0, newArray, 0, firstNull);
+ return newArray;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/extensions/IteratorExt.java b/src/main/java/gregtech/api/util/extensions/IteratorExt.java
new file mode 100644
index 0000000000..3cbae1c598
--- /dev/null
+++ b/src/main/java/gregtech/api/util/extensions/IteratorExt.java
@@ -0,0 +1,13 @@
+package gregtech.api.util.extensions;
+
+import java.util.Iterator;
+
+import gregtech.api.objects.iterators.MergedIterator;
+
+public class IteratorExt {
+
+ @SafeVarargs
+ public static <T> Iterator<T> merge(Iterator<T>... iterators) {
+ return new MergedIterator<>(iterators);
+ }
+}
diff --git a/src/main/java/gregtech/api/util/item/ItemHolder.java b/src/main/java/gregtech/api/util/item/ItemHolder.java
new file mode 100644
index 0000000000..4675d0ba0e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/item/ItemHolder.java
@@ -0,0 +1,79 @@
+package gregtech.api.util.item;
+
+import static net.minecraftforge.oredict.OreDictionary.getOreIDs;
+
+import java.util.Arrays;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import gregtech.api.util.GT_Utility;
+
+public class ItemHolder {
+
+ private final Item item;
+ private final int meta;
+ private final NBTTagCompound tag;
+ private final int[] oreIDs;
+
+ public ItemHolder(@Nonnull ItemStack item) {
+ this.item = item.getItem();
+ this.meta = Items.feather.getDamage(item);
+ this.tag = item.getTagCompound();
+ this.oreIDs = getOreIDs(item);
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public int getMeta() {
+ return meta;
+ }
+
+ public NBTTagCompound getNBT() {
+ return tag;
+ }
+
+ public int[] getOreDictTagIDs() {
+ return oreIDs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (!(other instanceof ItemHolder otherIH)) return false;
+ if (Arrays.stream(oreIDs)
+ .anyMatch(id -> {
+ for (int i = 0; i < otherIH.getOreDictTagIDs().length; i++) {
+ if (id == otherIH.getOreDictTagIDs()[i]) return true;
+ }
+ return false;
+ })) {
+ return true;
+ }
+
+ if (item != otherIH.getItem() || meta != otherIH.getMeta()) {
+ return false;
+ }
+ if (this.tag == null && otherIH.getNBT() == null) return true;
+ if (this.tag == null || otherIH.getNBT() == null) return false;
+ return this.tag.equals(otherIH);
+ }
+
+ @Override
+ public int hashCode() {
+ return GT_Utility.stackToInt(toStack());
+ }
+
+ @Nonnull
+ private ItemStack toStack() {
+ ItemStack item = new ItemStack(this.item, 1, meta);
+ item.stackTagCompound = tag;
+ return item;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/recipe/RecipeInputRequirements.java b/src/main/java/gregtech/api/util/recipe/RecipeInputRequirements.java
new file mode 100644
index 0000000000..590c104101
--- /dev/null
+++ b/src/main/java/gregtech/api/util/recipe/RecipeInputRequirements.java
@@ -0,0 +1,77 @@
+package gregtech.api.util.recipe;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.item.ItemHolder;
+
+public class RecipeInputRequirements {
+
+ protected Map<ItemHolder, Long> itemInputs = new HashMap<>();
+ protected Set<ItemHolder> itemInputsMet = new HashSet<>();
+ protected boolean metAllItem = false;
+ protected Map<Fluid, Long> fluidInputs = new HashMap<>();
+ protected Set<Fluid> fluidInputsMet = new HashSet<>();
+ protected boolean metAllFluid = false;
+
+ public RecipeInputRequirements(@Nonnull GT_Recipe recipe) {
+ this(recipe.mInputs, recipe.mFluidInputs);
+ }
+
+ public RecipeInputRequirements(@Nonnull ItemStack[] itemInputs, @Nonnull FluidStack[] fluidInputs) {
+ for (ItemStack item : itemInputs) {
+ if (item == null) continue;
+ ItemHolder itemIH = new ItemHolder(item);
+ this.itemInputs.put(itemIH, this.itemInputs.getOrDefault(itemIH, 0L) + item.stackSize);
+ }
+
+ for (FluidStack fluid : fluidInputs) {
+ if (fluid == null) continue;
+ this.fluidInputs.put(fluid.getFluid(), this.fluidInputs.getOrDefault(fluid.getFluid(), 0L) + fluid.amount);
+ }
+ }
+
+ /**
+ *
+ * @param itemInputs we have and want to fill this request
+ * @return {@code true} when all item inputs are met
+ */
+ public boolean tryToFillItemRequirements(Map<ItemHolder, Long> itemInputs) {
+ if (metAllItem) return metAllItem;
+ for (Entry<ItemHolder, Long> entry : itemInputs.entrySet()) {
+ if (itemInputsMet.contains(entry.getKey())) continue;
+ if (!this.itemInputs.containsKey(entry.getKey())) continue;
+ if (this.itemInputs.get(entry.getKey()) > entry.getValue()) continue;
+ itemInputsMet.add(entry.getKey());
+ }
+ metAllItem = itemInputsMet.containsAll(this.itemInputs.keySet());
+ return metAllItem;
+ }
+
+ /**
+ *
+ * @param fluidInputs we have and want to fill this request
+ * @return {@code true} when all fluid inputs are met
+ */
+ public boolean tryToFillFluidRequirements(Map<Fluid, Long> fluidInputs) {
+ if (metAllFluid) return metAllFluid;
+ for (Entry<Fluid, Long> entry : fluidInputs.entrySet()) {
+ if (fluidInputsMet.contains(entry.getKey())) continue;
+ if (!this.fluidInputs.containsKey(entry.getKey())) continue;
+ if (this.fluidInputs.get(entry.getKey()) > entry.getValue()) continue;
+ fluidInputsMet.add(entry.getKey());
+ }
+ metAllFluid = fluidInputsMet.containsAll(this.fluidInputs.keySet());
+ return metAllFluid;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/ReasonOutOfFluid.java b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfFluid.java
new file mode 100644
index 0000000000..29b99a644a
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfFluid.java
@@ -0,0 +1,63 @@
+package gregtech.api.util.shutdown;
+
+import static gregtech.api.util.GT_ModHandler.getWater;
+import static gregtech.api.util.GT_Utility.formatNumbers;
+
+import java.util.Objects;
+
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ReasonOutOfFluid implements ShutDownReason {
+
+ private FluidStack requiredFluid;
+
+ ReasonOutOfFluid(@NotNull FluidStack requiredFluid) {
+ this.requiredFluid = requiredFluid;
+ }
+
+ @NotNull
+ @Override
+ public String getID() {
+ return "out_of_fluid";
+ }
+
+ @NotNull
+ @Override
+ public String getDisplayString() {
+ return Objects.requireNonNull(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.out_of_fluid",
+ requiredFluid.getLocalizedName(),
+ formatNumbers(requiredFluid.amount)));
+ }
+
+ @NotNull
+ @Override
+ public ShutDownReason newInstance() {
+ return new ReasonOutOfFluid(getWater(0));
+ }
+
+ @Override
+ public void encode(@NotNull PacketBuffer buffer) {
+ buffer.writeInt(requiredFluid.getFluidID());
+ buffer.writeInt(requiredFluid.amount);
+ }
+
+ @Override
+ public void decode(PacketBuffer buffer) {
+ int fluidID = buffer.readInt();
+ Fluid fluid = FluidRegistry.getFluid(fluidID);
+ requiredFluid = new FluidStack(fluid, buffer.readInt());
+ }
+
+ @Override
+ public boolean wasCritical() {
+ return true;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/ReasonOutOfItem.java b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfItem.java
new file mode 100644
index 0000000000..f4a46f2d30
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfItem.java
@@ -0,0 +1,62 @@
+package gregtech.api.util.shutdown;
+
+import static gregtech.api.util.GT_Utility.formatNumbers;
+
+import java.util.Objects;
+
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.StatCollector;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ReasonOutOfItem implements ShutDownReason {
+
+ private ItemStack requiredItem;
+
+ ReasonOutOfItem(@NotNull ItemStack requiredItem) {
+ this.requiredItem = requiredItem;
+ }
+
+ @NotNull
+ @Override
+ public String getID() {
+ return "out_of_item";
+ }
+
+ @NotNull
+ @Override
+ public String getDisplayString() {
+ return Objects.requireNonNull(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.out_of_item",
+ requiredItem.getDisplayName(),
+ formatNumbers(requiredItem.stackSize)));
+ }
+
+ @NotNull
+ @Override
+ public ShutDownReason newInstance() {
+ return new ReasonOutOfItem(new ItemStack(Items.feather, 0));
+ }
+
+ @Override
+ public void encode(@NotNull PacketBuffer buffer) {
+ try {
+ buffer.writeItemStackToBuffer(requiredItem);
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public void decode(PacketBuffer buffer) {
+ try {
+ requiredItem = buffer.readItemStackFromBuffer();
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public boolean wasCritical() {
+ return true;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/ReasonOutOfStuff.java b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfStuff.java
new file mode 100644
index 0000000000..0c3f7e0b64
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/ReasonOutOfStuff.java
@@ -0,0 +1,61 @@
+package gregtech.api.util.shutdown;
+
+import static gregtech.api.util.GT_Utility.formatNumbers;
+
+import java.util.Objects;
+
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.StatCollector;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ReasonOutOfStuff implements ShutDownReason {
+
+ private String required;
+ private int amount;
+
+ ReasonOutOfStuff(@NotNull String required, int amount) {
+ this.required = required;
+ this.amount = amount;
+ }
+
+ @NotNull
+ @Override
+ public String getID() {
+ return "out_of_stuff";
+ }
+
+ @NotNull
+ @Override
+ public String getDisplayString() {
+ return Objects.requireNonNull(
+ StatCollector.translateToLocalFormatted("GT5U.gui.text.out_of_stuff", required, formatNumbers(amount)));
+ }
+
+ @NotNull
+ @Override
+ public ShutDownReason newInstance() {
+ return new ReasonOutOfStuff("stuff", 1);
+ }
+
+ @Override
+ public void encode(@NotNull PacketBuffer buffer) {
+ buffer.writeInt(amount);
+ try {
+ buffer.writeStringToBuffer(required);
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public void decode(PacketBuffer buffer) {
+ amount = buffer.readInt();
+ try {
+ required = buffer.readStringFromBuffer(32768);
+ } catch (Exception ignored) {}
+ }
+
+ @Override
+ public boolean wasCritical() {
+ return true;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/ShutDownReason.java b/src/main/java/gregtech/api/util/shutdown/ShutDownReason.java
new file mode 100644
index 0000000000..0815936a55
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/ShutDownReason.java
@@ -0,0 +1,41 @@
+package gregtech.api.util.shutdown;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.network.PacketBuffer;
+
+public interface ShutDownReason {
+
+ /**
+ * @return Unique registry ID
+ */
+ @Nonnull
+ String getID();
+
+ /**
+ * @return Actual text to show on client GUI
+ */
+ @Nonnull
+ String getDisplayString();
+
+ /**
+ * Create new instance to receive packet.
+ */
+ @Nonnull
+ ShutDownReason newInstance();
+
+ /**
+ * Encode value to sync.
+ */
+ void encode(@Nonnull PacketBuffer buffer);
+
+ /**
+ * Decode synced value.
+ */
+ void decode(PacketBuffer buffer);
+
+ /**
+ * @return Whether the reason is critical.
+ */
+ boolean wasCritical();
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/ShutDownReasonRegistry.java b/src/main/java/gregtech/api/util/shutdown/ShutDownReasonRegistry.java
new file mode 100644
index 0000000000..298c5db237
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/ShutDownReasonRegistry.java
@@ -0,0 +1,118 @@
+package gregtech.api.util.shutdown;
+
+import static gregtech.api.util.GT_ModHandler.getWater;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+public class ShutDownReasonRegistry {
+
+ private static final Map<String, ShutDownReason> registry = new HashMap<>();
+
+ /**
+ * Registers ShutDownReason. No duplicated IDs are allowed.
+ *
+ * @param sample Sample object to register
+ */
+ public static void register(ShutDownReason sample) {
+ if (isRegistered(sample.getID())) {
+ throw new IllegalStateException(
+ String.format(
+ "ID %s is already registered for %s",
+ sample.getID(),
+ registry.get(sample.getID())
+ .getClass()
+ .getCanonicalName()));
+ }
+ registry.put(sample.getID(), sample);
+ }
+
+ public static ShutDownReason getSampleFromRegistry(String id) {
+ if (!isRegistered(id)) {
+ throw new RuntimeException("Unknown id: " + id);
+ }
+ return registry.get(id);
+ }
+
+ public static boolean isRegistered(String id) {
+ return registry.containsKey(id);
+ }
+
+ /**
+ * Shut down due to power loss.
+ */
+ @Nonnull
+ public static final ShutDownReason POWER_LOSS = SimpleShutDownReason.ofCritical("power_loss");
+ /**
+ * Failed to output the pollution.
+ */
+ @Nonnull
+ public static final ShutDownReason POLLUTION_FAIL = SimpleShutDownReason.ofCritical("pollution_fail");
+ /**
+ * Shut down due to incomplete structure.
+ */
+ @Nonnull
+ public static final ShutDownReason STRUCTURE_INCOMPLETE = SimpleShutDownReason.ofNormal("structure_incomplete");
+ /**
+ * Shut down due to machine damage.
+ */
+ @Nonnull
+ public static final ShutDownReason NO_REPAIR = SimpleShutDownReason.ofNormal("no_repair");
+ /**
+ * No valid turbine found.
+ */
+ @Nonnull
+ public static final ShutDownReason NO_TURBINE = SimpleShutDownReason.ofNormal("no_turbine");
+ /**
+ * No correct machine part in controller slot.
+ */
+ @Nonnull
+ public static final ShutDownReason NO_MACHINE_PART = SimpleShutDownReason.ofNormal("no_machine_part");
+ /**
+ * Default unknown state.
+ */
+ @Nonnull
+ public static final ShutDownReason NONE = SimpleShutDownReason.ofNormal("none");
+ /**
+ * Critical unknown state.
+ */
+ @Nonnull
+ public static final ShutDownReason CRITICAL_NONE = SimpleShutDownReason.ofCritical("none");
+
+ /**
+ * Fluid that needs to be constantly supplied are out. E.g. PCB coolant with cooling upgrades enabled.
+ */
+ @Nonnull
+ public static ShutDownReason outOfFluid(@Nonnull FluidStack required) {
+ return new ReasonOutOfFluid(required);
+ }
+
+ /**
+ * Item that needs to be constantly supplied are out.
+ */
+ @Nonnull
+ public static ShutDownReason outOfItem(@Nonnull ItemStack required) {
+ return new ReasonOutOfItem(required);
+ }
+
+ /**
+ * Stuff that needs to be constantly supplied are out.
+ */
+ @Nonnull
+ public static ShutDownReason outOfStuff(@Nonnull String required, int amount) {
+ return new ReasonOutOfStuff(required, amount);
+ }
+
+ static {
+ register(new SimpleShutDownReason("", false));
+ register(new ReasonOutOfFluid(getWater(0)));
+ register(new ReasonOutOfItem(new ItemStack(Items.feather, 1)));
+ register(new ReasonOutOfStuff("stuff", 1));
+ }
+}
diff --git a/src/main/java/gregtech/api/util/shutdown/SimpleShutDownReason.java b/src/main/java/gregtech/api/util/shutdown/SimpleShutDownReason.java
new file mode 100644
index 0000000000..92763fa431
--- /dev/null
+++ b/src/main/java/gregtech/api/util/shutdown/SimpleShutDownReason.java
@@ -0,0 +1,79 @@
+package gregtech.api.util.shutdown;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.StatCollector;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+
+/**
+ * Simple implementation of {@link ShutDownReason}. You can create new object without registering it.
+ */
+public class SimpleShutDownReason implements ShutDownReason {
+
+ private String key;
+ private boolean wasCritical;
+
+ public SimpleShutDownReason(String key, boolean isCritical) {
+ this.key = key;
+ this.wasCritical = isCritical;
+ }
+
+ @NotNull
+ @Override
+ public String getID() {
+ return "simple_result";
+ }
+
+ @NotNull
+ @Override
+ public String getDisplayString() {
+ return Objects.requireNonNull(StatCollector.translateToLocal("GT5U.gui.text." + key));
+ }
+
+ @NotNull
+ @Override
+ public ShutDownReason newInstance() {
+ return new SimpleShutDownReason("", false);
+ }
+
+ @Override
+ public void encode(@NotNull PacketBuffer buffer) {
+ buffer.writeBoolean(wasCritical);
+ NetworkUtils.writeStringSafe(buffer, key);
+ }
+
+ @Override
+ public void decode(PacketBuffer buffer) {
+ wasCritical = buffer.readBoolean();
+ key = NetworkUtils.readStringSafe(buffer);
+ }
+
+ @Override
+ public boolean wasCritical() {
+ return wasCritical;
+ }
+
+ /**
+ * Creates new reason with critical state. Add your localized description with `GT5U.gui.text.{key}`.
+ * This is already registered to registry.
+ */
+ @Nonnull
+ public static ShutDownReason ofCritical(String key) {
+ return new SimpleShutDownReason(key, true);
+ }
+
+ /**
+ * Creates new reason with normal state. Add your localized description with `GT5U.gui.text.{key}`.
+ * This is already registered to registry.
+ */
+ @Nonnull
+ public static ShutDownReason ofNormal(String key) {
+ return new SimpleShutDownReason(key, false);
+ }
+}