aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dependencies.gradle1
-rw-r--r--src/functionalTest/java/kubatech/test/EIGTests.java227
-rw-r--r--src/main/java/gregtech/mixin/Mixin.java7
-rw-r--r--src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java23
-rw-r--r--src/main/java/kubatech/CommonProxy.java2
-rw-r--r--src/main/java/kubatech/api/EIGDynamicInventory.java510
-rw-r--r--src/main/java/kubatech/api/IBlockStemAccesor.java8
-rw-r--r--src/main/java/kubatech/api/eig/EIGBucket.java247
-rw-r--r--src/main/java/kubatech/api/eig/EIGDropTable.java224
-rw-r--r--src/main/java/kubatech/api/eig/EIGMode.java154
-rw-r--r--src/main/java/kubatech/api/eig/IEIGBucketFactory.java15
-rw-r--r--src/main/java/kubatech/api/enums/EIGModes.java42
-rw-r--r--src/main/java/kubatech/api/enums/EIGSetupPhase.java16
-rw-r--r--src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java72
-rw-r--r--src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java6
-rw-r--r--src/main/java/kubatech/api/utils/StringUtils.java6
-rw-r--r--src/main/java/kubatech/loaders/EIGBucketLoader.java27
-rw-r--r--src/main/java/kubatech/loaders/RecipeLoader.java11
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java1570
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java73
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java905
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java77
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java286
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java158
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java95
-rw-r--r--src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java91
-rw-r--r--src/main/resources/assets/kubatech/lang/en_US.lang3
27 files changed, 3791 insertions, 1065 deletions
diff --git a/dependencies.gradle b/dependencies.gradle
index d4d2bdf0f5..04d535fce8 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -61,6 +61,7 @@ dependencies {
compileOnlyApi("com.github.GTNewHorizons:Railcraft:9.15.8:dev") { transitive = false }
compileOnly("TGregworks:TGregworks:1.7.10-GTNH-1.0.26:deobf") {transitive = false}
+ compileOnly("com.github.GTNewHorizons:ThaumicBases:1.6.4:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:EnderCore:0.4.6:dev") { transitive = false }
implementation("com.github.GTNewHorizons:Galacticraft:3.1.5-GTNH:dev") { transitive = false }
implementation("com.github.GTNewHorizons:TinkersConstruct:1.12.2-GTNH:dev")
diff --git a/src/functionalTest/java/kubatech/test/EIGTests.java b/src/functionalTest/java/kubatech/test/EIGTests.java
index 1bb88814a4..7cdfa8dae4 100644
--- a/src/functionalTest/java/kubatech/test/EIGTests.java
+++ b/src/functionalTest/java/kubatech/test/EIGTests.java
@@ -20,15 +20,10 @@
package kubatech.test;
-import static gregtech.api.util.GT_RecipeBuilder.MINUTES;
-import static gregtech.api.util.GT_RecipeBuilder.SECONDS;
-import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_ACCELERATOR_TIER;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.fail;
+import static gregtech.api.util.GT_RecipeBuilder.HOURS;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
-import java.util.Arrays;
-import java.util.Comparator;
import java.util.Map;
import net.minecraft.block.Block;
@@ -37,7 +32,6 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
-import net.minecraft.util.ChatComponentText;
import net.minecraft.world.MinecraftException;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
@@ -45,6 +39,7 @@ import net.minecraft.world.WorldProviderSurface;
import net.minecraft.world.WorldServer;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldType;
+import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.chunk.storage.IChunkLoader;
import net.minecraft.world.storage.IPlayerFileData;
import net.minecraft.world.storage.ISaveHandler;
@@ -53,8 +48,6 @@ import net.minecraftforge.common.DimensionManager;
import org.junit.jupiter.api.Test;
-import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
-
import gregtech.api.GregTech_API;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.common.blocks.GT_Item_Machines;
@@ -62,13 +55,16 @@ import ic2.api.crops.CropCard;
import ic2.api.crops.Crops;
import ic2.core.Ic2Items;
import ic2.core.crop.TileEntityCrop;
+import ic2.core.item.ItemCropSeed;
+import kubatech.api.eig.EIGDropTable;
import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket;
public class EIGTests {
private static final int EIG_CONTROLLER_METADATA = 12_792;
- private static final int NUMBER_OF_CROPS_PER_TEST = 90;
- private static final int NUMBER_OF_TESTS_TO_DO = 30;
+ private static final int EIG_SIMULATION_TIME = 24 * HOURS;
+ private static final int NUMBER_OF_TESTS_TO_DO = 1000;
static World myWorld;
@@ -144,85 +140,73 @@ public class EIGTests {
public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) {
return 4;
}
+
+ @Override
+ public BiomeGenBase getBiomeGenForCoords(int x, int z) {
+ // give the environment a fighting chance of being bearable for crops
+ return BiomeGenBase.jungle;
+ }
};
}
}
- private static int leftOverTicksFromRealRun = 0;
-
- ItemStackMap<Integer> getRealDrops(TileEntityCrop cropTile, CropCard cc, int growth, int gain, int resistance) {
+ EIGDropTable getRealDrops(TileEntityCrop cropTile, CropCard cc, int growth, int gain, int resistance) {
cropTile.setCrop(cc);
cropTile.setGrowth((byte) growth);
cropTile.setGain((byte) gain);
cropTile.setResistance((byte) resistance);
- cropTile.tick();
-
- ItemStackMap<Integer> expected = new ItemStackMap<>();
-
- // run for 30 minutes
- for (int k = 0; k < NUMBER_OF_CROPS_PER_TEST; k++) {
- cropTile.ticker = 1;
- cropTile.setSize((byte) cc.maxSize());
- cropTile.setSize(cc.getSizeAfterHarvest(cropTile));
- cropTile.growthPoints = 0;
- int lastHarvestedAt = 0;
- int i;
- for (i = 0; i < (30 * MINUTES) * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER);) {
- i += TileEntityCrop.tickRate;
- cropTile.tick();
- if (!cc.canGrow(cropTile)) {
- lastHarvestedAt = i;
- ItemStack[] stacks = cropTile.harvest_automated(false);
- for (ItemStack stack : stacks) {
- expected.merge(stack, stack.stackSize, Integer::sum);
- }
+ EIGDropTable expected = new EIGDropTable();
+ byte startingHumidity = cropTile.humidity;
+ byte startingNutrients = cropTile.nutrients;
+ byte startingAirQuality = cropTile.airQuality;
+ int startingNutrientStorage = cropTile.nutrientStorage;
+ int startingWaterStorage = cropTile.waterStorage;
+ // reset the crop to it's stage after harvest
+ cropTile.setSize((byte) cc.maxSize());
+ cropTile.setSize(cc.getSizeAfterHarvest(cropTile));
+ for (int timeElapsed = 0; timeElapsed < EIG_SIMULATION_TIME; timeElapsed += TileEntityCrop.tickRate) {
+ // force reset the stats to max because the eig shouldn't make them change.
+ // some crops check water storage in the can grow and we are ticking which consumes water.
+ cropTile.humidity = startingHumidity;
+ cropTile.nutrients = startingNutrients;
+ cropTile.airQuality = startingAirQuality;
+ cropTile.nutrientStorage = startingNutrientStorage;
+ cropTile.waterStorage = startingWaterStorage;
+ // if fully grown harvest the crop
+ if (cropTile.getSize() >= cc.maxSize()) {
+ ItemStack[] stacks = cropTile.harvest_automated(false);
+ for (ItemStack stack : stacks) {
+ expected.addDrop(stack, stack.stackSize);
}
}
- leftOverTicksFromRealRun += i - lastHarvestedAt;
+ cropTile.tick();
}
-
+ // ensure it leaves the same way it came in
+ cropTile.humidity = startingHumidity;
+ cropTile.nutrients = startingNutrients;
+ cropTile.airQuality = startingAirQuality;
+ cropTile.nutrientStorage = startingNutrientStorage;
+ cropTile.waterStorage = startingWaterStorage;
return expected;
}
- ItemStackMap<Integer> getEIGDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG, ItemStack stack) {
- ItemStackMap<Integer> generated = new ItemStackMap<>();
- int imax = (30 * MINUTES) / (5 * SECONDS);
- double ticks_to_ignore_per_operation = Math
- .ceil((double) leftOverTicksFromRealRun / (NUMBER_OF_CROPS_PER_TEST * imax));
- for (int j = 0; j < NUMBER_OF_CROPS_PER_TEST; j++) {
- GT_MetaTileEntity_ExtremeIndustrialGreenhouse.GreenHouseSlot slot = new GT_MetaTileEntity_ExtremeIndustrialGreenhouse.GreenHouseSlot(
- EIG,
- stack.copy(),
- true,
- false);
- if (slot.isValid) {
- for (int i = 0; i < imax; i++) {
- int ticks_to_ignore = (int) Math.min(ticks_to_ignore_per_operation, leftOverTicksFromRealRun);
- leftOverTicksFromRealRun -= ticks_to_ignore;
- for (ItemStack ic2Drop : slot.getIC2Drops(
- EIG,
- (5 * SECONDS * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER)) - (double) ticks_to_ignore)) {
- generated.merge(ic2Drop, ic2Drop.stackSize, Integer::sum);
- }
- }
- }
- }
-
+ EIGDropTable getEIGDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG, ItemStack stack) {
+ EIGDropTable generated = new EIGDropTable();
+ EIGIC2Bucket bucket = new EIGIC2Bucket(stack, stack.stackSize, null, false);
+ bucket.revalidate(EIG);
+ bucket.addProgress(EIG_SIMULATION_TIME, generated);
return generated;
}
@Test
void EIGDrops() {
-
myWorld.setBlock(10, 80, 0, Blocks.farmland, 0, 0);
myWorld.setBlock(10, 81, 0, Block.getBlockFromItem(Ic2Items.crop.getItem()), 0, 0);
- CropCard cc = Crops.instance.getCropCard("gregtech", "Indigo");
+ // using stickreed since it has a random stage after harvest.
+ // it's also more preferable to test using faster growing crops since they can be harvested more often.
+ CropCard cc = Crops.instance.getCropCard("IC2", "stickreed");
TileEntityCrop cropTile = (TileEntityCrop) myWorld.getTileEntity(10, 81, 0);
- ItemStack ccStack = cropTile.generateSeeds(cc, (byte) 10, (byte) 10, (byte) 10, (byte) 1);
- for (int i = 0; i < TileEntityCrop.tickRate; i++) {
- cropTile.waterStorage = 200;
- cropTile.updateEntity();
- }
+ ItemStack ccStack = ItemCropSeed.generateItemStackFromValues(cc, (byte) 10, (byte) 10, (byte) 10, (byte) 1);
GT_Item_Machines itemMachines = (GT_Item_Machines) Item.getItemFromBlock(GregTech_API.sBlockMachines);
itemMachines.placeBlockAt(
@@ -241,6 +225,11 @@ public class EIGTests {
GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG = (GT_MetaTileEntity_ExtremeIndustrialGreenhouse) te
.getMetaTileEntity();
+ // update stats of crop TE to those provided by the EIG
+ cropTile.humidity = EIGIC2Bucket.getHumidity(EIG, false);
+ cropTile.nutrients = EIGIC2Bucket.getNutrients(EIG);
+ cropTile.airQuality = EIGIC2Bucket.getAirQuality(EIG);
+
int[] abc = new int[] { 0, -2, 3 };
int[] xyz = new int[] { 0, 0, 0 };
EIG.getExtendedFacing()
@@ -254,89 +243,35 @@ public class EIGTests {
ItemStack stackToTest = null;
- for (int n = 0; n < 5; n++) {
-
- int[] x = new int[NUMBER_OF_TESTS_TO_DO];
- int[] y = new int[NUMBER_OF_TESTS_TO_DO];
-
- // MinecraftServer.getServer()
- // .addChatMessage(new ChatComponentText("[EIGTest results]"));
-
- for (int i = 0; i < NUMBER_OF_TESTS_TO_DO; i++) {
- leftOverTicksFromRealRun = 0;
- ItemStackMap<Integer> expected = getRealDrops(cropTile, cc, 10, 10, 10);
- ItemStackMap<Integer> generated = getEIGDrops(EIG, ccStack);
-
- // MinecraftServer.getServer()
- // .addChatMessage(new ChatComponentText("[TEST" + i + "]Real crop drops:"));
- // for (Map.Entry<ItemStack, Integer> entry : expected.entrySet()) {
- // MinecraftServer.getServer()
- // .addChatMessage(new ChatComponentText("- " + entry.getKey().getDisplayName() + " x" +
- // entry.getValue()));
- // }
-
- // MinecraftServer.getServer()
- // .addChatMessage(new ChatComponentText("[TEST" + i + "]EIG crop drops:"));
- // for (Map.Entry<ItemStack, Integer> entry : generated.entrySet()) {
- // MinecraftServer.getServer()
- // .addChatMessage(new ChatComponentText("- " + entry.getKey().getDisplayName() + " x" +
- // entry.getValue()));
- // }
-
- // we are only comparing one item from drops
- if (stackToTest == null) {
- stackToTest = expected.entrySet()
- .stream()
- .max(Comparator.comparingInt(Map.Entry::getValue))
- .get()
- .getKey();
- }
+ double realAvg = 0, eigAvg = 0;
- int expectedValue = expected.getOrDefault(stackToTest, 0);
- int generatedValue = generated.getOrDefault(stackToTest, 0);
+ for (int i = 0; i < NUMBER_OF_TESTS_TO_DO; i++) {
+ EIGDropTable expected = getRealDrops(cropTile, cc, 10, 10, 10);
+ EIGDropTable generated = getEIGDrops(EIG, ccStack);
- x[i] = expectedValue;
- y[i] = generatedValue;
+ // we are only comparing one item from drops
+ if (stackToTest == null) {
+ stackToTest = expected.entrySet()
+ .stream()
+ .max(Map.Entry.comparingByValue())
+ .get()
+ .getKey();
}
- double real_average = Arrays.stream(x)
- .average()
- .getAsDouble();
- double eig_average = Arrays.stream(y)
- .average()
- .getAsDouble();
-
- double real_variance = 0d;
- double a = 0d;
- for (int i : x) {
- a += (i - real_average) * (i - real_average);
- }
- a /= NUMBER_OF_TESTS_TO_DO;
- real_variance = a;
-
- double eig_variance = 0d;
- a = 0d;
- for (int i : y) {
- a += (i - eig_average) * (i - eig_average);
- }
- a /= NUMBER_OF_TESTS_TO_DO;
- eig_variance = a;
-
- double u = (real_average - eig_average)
- / Math.sqrt((real_variance / NUMBER_OF_TESTS_TO_DO) + (eig_variance / NUMBER_OF_TESTS_TO_DO));
- MinecraftServer.getServer()
- .addChatMessage(
- new ChatComponentText(
- "real average = " + Math
- .round(real_average) + " eig average = " + Math.round(eig_average) + " u = " + u));
- double test_critical_value = 1.959964d;
- boolean passed = Math.abs(u) < test_critical_value;
- boolean instafail = Math.abs(u) > test_critical_value * 2;
- if (passed) return;
- assertFalse(instafail);
+ realAvg += expected.getItemAmount(stackToTest);
+ // EIG with ic2 crops doesn't actually have variance, it uses very precise approximations that create
+ // accurate growth rate and drop quality approximations.
+ eigAvg += generated.getItemAmount(stackToTest);
}
- fail();
+ realAvg /= NUMBER_OF_TESTS_TO_DO;
+ eigAvg /= NUMBER_OF_TESTS_TO_DO;
+ double accuracy = Math.min(realAvg / eigAvg, eigAvg / realAvg);
+
+ String debugInfo = String.format("realAvg: %.5f | eigAvg : %.5f | accuracy = %.5f", realAvg, eigAvg, accuracy);
+ System.out.println(debugInfo);
+ // We aim for about 99% accuracy over here.
+ assertTrue(accuracy >= 0.99d);
}
}
diff --git a/src/main/java/gregtech/mixin/Mixin.java b/src/main/java/gregtech/mixin/Mixin.java
index 6e2b927afe..2fb3e55350 100644
--- a/src/main/java/gregtech/mixin/Mixin.java
+++ b/src/main/java/gregtech/mixin/Mixin.java
@@ -43,6 +43,11 @@ public enum Mixin {
.setApplyIf(() -> ConfigHandler.enabledPatches[3])
.setPhase(Phase.EARLY)
.setSide(Side.BOTH)),
+ BlockStemMixin(new Builder("Stem Crop Block Accessor").addMixinClasses("minecraft.BlockStemMixin")
+ .addTargetedMod(VANILLA)
+ .setApplyIf(() -> true)
+ .setPhase(Phase.EARLY)
+ .setSide(Side.BOTH)),
// Extra utilities
RemoveLastMilleniumRain(new Builder("Remove rain from the Last Millenium (Extra Utilities)")
.addMixinClasses("xu.WorldProviderEndOfTimeMixin")
@@ -62,7 +67,7 @@ public enum Mixin {
.addTargetedMod(THAUMCRAFT)
.setApplyIf(() -> ConfigHandler.enabledPatches[2])
.setPhase(Phase.LATE)
- .setSide(Side.BOTH)),;
+ .setSide(Side.BOTH));
public static final Logger LOGGER = LogManager.getLogger("GregTech-Mixin");
diff --git a/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java b/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java
new file mode 100644
index 0000000000..685e00b2a8
--- /dev/null
+++ b/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java
@@ -0,0 +1,23 @@
+package gregtech.mixin.mixins.early.minecraft;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockStem;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import kubatech.api.IBlockStemAccesor;
+
+@Mixin(value = BlockStem.class)
+public class BlockStemMixin implements IBlockStemAccesor {
+
+ @Shadow
+ @Final
+ private Block field_149877_a;
+
+ @Override
+ public Block getCropBlock() {
+ return this.field_149877_a;
+ }
+}
diff --git a/src/main/java/kubatech/CommonProxy.java b/src/main/java/kubatech/CommonProxy.java
index 674b87f95d..20a10b2a52 100644
--- a/src/main/java/kubatech/CommonProxy.java
+++ b/src/main/java/kubatech/CommonProxy.java
@@ -39,6 +39,7 @@ import cpw.mods.fml.common.event.FMLServerStoppedEvent;
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
import kubatech.commands.CommandHandler;
import kubatech.config.Config;
+import kubatech.loaders.EIGBucketLoader;
import kubatech.loaders.MTLoader;
import kubatech.loaders.MobHandlerLoader;
import kubatech.loaders.RecipeLoader;
@@ -67,6 +68,7 @@ public class CommonProxy {
public void postInit(FMLPostInitializationEvent event) {
RecipeLoader.addRecipes();
+ EIGBucketLoader.LoadEIGBuckets();
if (Thaumcraft.isModLoaded()) TCLoader.init();
}
diff --git a/src/main/java/kubatech/api/EIGDynamicInventory.java b/src/main/java/kubatech/api/EIGDynamicInventory.java
new file mode 100644
index 0000000000..1c703fe2fa
--- /dev/null
+++ b/src/main/java/kubatech/api/EIGDynamicInventory.java
@@ -0,0 +1,510 @@
+package kubatech.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+
+import org.lwjgl.opengl.GL11;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.internal.Theme;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.ChangeableWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+import com.kuba6000.mobsinfo.api.utils.ItemID;
+
+import kubatech.api.gui.AutoScalingStackSizeText;
+import kubatech.api.helpers.GTHelper;
+import kubatech.api.utils.ModUtils;
+
+public class EIGDynamicInventory<T> {
+
+ int width, height;
+ Supplier<Integer> maxSeedCountGetter;
+ Supplier<Integer> maxSeedTypeGetter;
+ Supplier<Integer> usedSeedCountGetter;
+ Supplier<Integer> usedSeedTypesGetter;
+ private int maxSeedTypes = 0;
+ private int maxSeedCount = 0;
+ private int usedSeedTypes = 0;
+ private int usedSeedCount = 0;
+ List<T> inventory;
+ TInventoryGetter<T> inventoryGetter;
+ TInventoryInjector inventoryInjector = null;
+ TInventoryExtractor<T> inventoryExtractor = null;
+ TInventoryReplacerOrMerger inventoryReplacer = null;
+ Supplier<Boolean> isEnabledGetter = null;
+ boolean isEnabled = true;
+
+ public EIGDynamicInventory(int width, int height, Supplier<Integer> maxSeedTypeGetter,
+ Supplier<Integer> maxSeedCountGetter, Supplier<Integer> usedSeedTypesGetter,
+ Supplier<Integer> usedSeedCountGetter, List<T> inventory, TInventoryGetter<T> inventoryGetter) {
+ this.width = width;
+ this.height = height;
+ this.maxSeedTypeGetter = maxSeedTypeGetter;
+ this.maxSeedCountGetter = maxSeedCountGetter;
+ this.usedSeedTypesGetter = usedSeedTypesGetter;
+ this.usedSeedCountGetter = usedSeedCountGetter;
+ this.inventory = inventory;
+ this.inventoryGetter = inventoryGetter;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryInjection(TInventoryInjector inventoryInjector) {
+ this.inventoryInjector = inventoryInjector;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryExtraction(TInventoryExtractor<T> inventoryExtractor) {
+ this.inventoryExtractor = inventoryExtractor;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> allowInventoryReplace(TInventoryReplacerOrMerger inventoryReplacer) {
+ this.inventoryReplacer = inventoryReplacer;
+ return this;
+ }
+
+ public EIGDynamicInventory<T> setEnabled(Supplier<Boolean> isEnabled) {
+ this.isEnabledGetter = isEnabled;
+ return this;
+ }
+
+ public UITexture getItemSlot() {
+ return ModularUITextures.ITEM_SLOT;
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ public Widget asWidget(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ ChangeableWidget container = new ChangeableWidget(() -> createWidget(buildContext.getPlayer()));
+ // TODO: Only reset the widget when there are more slot stacks, otherwise just refresh them somehow
+
+ container
+ // max seed types
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.maxSeedTypeGetter.get();
+ if (this.maxSeedTypes != i) {
+ this.maxSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.maxSeedTypes != i) {
+ this.maxSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // used seed types
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.usedSeedTypesGetter.get();
+ if (this.usedSeedTypes != i) {
+ this.usedSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.usedSeedTypes != i) {
+ this.usedSeedTypes = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // max seed count
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.maxSeedCountGetter.get();
+ if (this.maxSeedCount != i) {
+ this.maxSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.maxSeedCount != i) {
+ this.maxSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+ // used seed count
+ .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> {
+ int i = this.usedSeedCountGetter.get();
+ if (this.usedSeedCount != i) {
+ this.usedSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ return i;
+ }, i -> {
+ if (this.usedSeedCount != i) {
+ this.usedSeedCount = i;
+ container.notifyChangeNoSync();
+ }
+ }), builder)
+
+ .attachSyncer(new FakeSyncWidget.ListSyncer<>(() -> {
+ List<GTHelper.StackableItemSlot> newDrawables = new ArrayList<>();
+ for (int i = 0, mStorageSize = inventory.size(); i < mStorageSize; i++) {
+ T slot = inventory.get(i);
+ if (slot == null) {
+ continue;
+ }
+ ItemStack stack = inventoryGetter.get(slot);
+ newDrawables
+ .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList<>(Collections.singletonList(i))));
+ }
+ if (!Objects.equals(newDrawables, drawables)) {
+ drawables = newDrawables;
+ container.notifyChangeNoSync();
+ }
+ return drawables;
+ }, l -> {
+ drawables.clear();
+ drawables.addAll(l);
+ container.notifyChangeNoSync();
+ }, (buffer, i) -> {
+ try {
+ i.write(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, buffer -> {
+ try {
+ return GTHelper.StackableItemSlot.read(buffer);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }), builder);
+ if (isEnabledGetter != null) {
+ container.attachSyncer(new FakeSyncWidget.BooleanSyncer(isEnabledGetter, i -> isEnabled = i), builder);
+ }
+ return container;
+ }
+
+ List<GTHelper.StackableItemSlot> drawables = new ArrayList<>();
+
+ private Widget createWidget(EntityPlayer player) {
+ Scrollable dynamicInventoryWidget = new Scrollable().setVerticalScroll();
+
+ ArrayList<Widget> buttons = new ArrayList<>();
+
+ if (!ModUtils.isClientThreaded()) {
+ HashMap<ItemID, Integer> itemMap = new HashMap<>();
+ HashMap<ItemID, ItemStack> stackMap = new HashMap<>();
+ HashMap<ItemID, ArrayList<Integer>> realSlotMap = new HashMap<>();
+ drawables = new ArrayList<>();
+ for (int i = 0, inventorySize = inventory.size(); i < inventorySize; i++) {
+ T slot = inventory.get(i);
+ if (slot == null) {
+ continue;
+ }
+ ItemStack stack = inventoryGetter.get(slot);
+ drawables
+ .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList<Integer>(Collections.singleton(i))));
+ }
+ }
+
+ for (int ID = 0; ID < drawables.size(); ID++) {
+ final int finalID = ID;
+
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+
+ if (clickData.mouseButton == 2) {
+ // special button handler goes here
+ if (drawables.size() <= finalID) return;
+ if (player.capabilities.isCreativeMode && player.inventory.getItemStack() == null) {
+ int realID = drawables.get(finalID).realSlots.get(0);
+ ItemStack stack = inventoryGetter.get(inventory.get(realID))
+ .copy();
+ stack.stackSize = stack.getMaxStackSize();
+ player.inventory.setItemStack(stack);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ } else if (clickData.shift) {
+ if (inventoryExtractor == null) return;
+ if (drawables.size() <= finalID) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T toRemoveFrom = this.inventory.get(realID);
+ ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player);
+ if (removed != null) {
+ if (player.inventory.addItemStackToInventory(removed))
+ player.inventoryContainer.detectAndSendChanges();
+ else player.entityDropItem(removed, 0.f);
+ return;
+ }
+ } else {
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (inventoryInjector == null) return;
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ inventoryInjector.inject(copy);
+ if (copy.stackSize == 1) return;
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ inventoryInjector.inject(input);
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ if (drawables.size() > finalID) {
+ if (inventoryExtractor == null) return;
+ int realID = drawables.get(finalID).realSlots.get(0);
+ T toRemoveFrom = this.inventory.get(realID);
+ ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player);
+ if (removed != null) {
+ player.inventory.setItemStack(removed);
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ }
+ }
+ })
+ .setBackground(() -> {
+ ItemStack stack = drawables.get(finalID).stack;
+ float slotSize = 16.0f;
+ IDrawable itemDrawable = new ItemDrawable(stack).withFixedSize(slotSize, slotSize, 1, 1);
+ IDrawable stackSizeText = new AutoScalingStackSizeText(stack.stackSize).color(Color.WHITE.normal)
+ .shadow()
+ .alignment(Alignment.BottomRight)
+ .measure();
+
+ return new IDrawable[] { getItemSlot(), itemDrawable, stackSizeText };
+ })
+ .dynamicTooltip(() -> {
+ if (drawables.size() > finalID) {
+ ItemStack stack = drawables.get(finalID).stack;
+ List<String> tip = new LinkedList<>();
+ for (Object o : stack.getTooltip(player, false)) {
+ tip.add(o.toString());
+ }
+ if (tip.size() >= 1 && tip.get(0) != null) {
+ tip.set(0, stack.stackSize + " x " + tip.get(0));
+ }
+ return tip;
+ }
+ return Collections.emptyList();
+ })
+ .setSize(18, 18));
+ }
+
+ // only add the extra slot if we are still able to insert
+ if (this.usedSeedCount < this.maxSeedCount) {
+ buttons.add(new ButtonWidget() {
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ super.drawBackground(partialTicks);
+ if (!isEnabled) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80));
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ // Copied from SlotWidget#draw
+ else if (isHovering() && !getContext().getCursor()
+ .hasDraggable()) {
+ GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glEnable(GL11.GL_BLEND);
+ GlStateManager.colorMask(true, true, true, false);
+ ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight());
+ GlStateManager.colorMask(true, true, true, true);
+ GL11.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }.setPlayClickSound(false)
+ .setOnClick((clickData, widget) -> {
+ if (!(player instanceof EntityPlayerMP)) return;
+ if (!isEnabledGetter.get()) return;
+ ItemStack input = player.inventory.getItemStack();
+ if (input != null) {
+ if (clickData.mouseButton == 1) {
+ ItemStack copy = input.copy();
+ copy.stackSize = 1;
+ inventoryInjector.inject(copy);
+ if (copy.stackSize == 1) return;
+
+ input.stackSize--;
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ } else {
+ inventoryInjector.inject(input);
+ if (input.stackSize > 0) {
+ // clearing and updating the held item value like this is the only
+ // way i found to be able to reliably update the item count in the UI.
+ player.inventory.setItemStack(null);
+ ((EntityPlayerMP) player).updateHeldItem();
+ player.inventory.setItemStack(input);
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ } else player.inventory.setItemStack(null);
+ }
+ ((EntityPlayerMP) player).isChangingQuantityOnly = false;
+ ((EntityPlayerMP) player).updateHeldItem();
+ return;
+ }
+ })
+ .setBackground(() -> {
+ IDrawable itemSlot = getItemSlot();
+
+ IDrawable stackSizeText = new AutoScalingStackSizeText(this.maxSeedCount - this.usedSeedCount)
+ .color(Color.WHITE.normal)
+ .shadow()
+ .alignment(Alignment.BottomRight)
+ .measure();
+
+ return new IDrawable[] { itemSlot, stackSizeText };
+ })
+ .dynamicTooltip(() -> {
+ // TODO: all l10n for insertion slot tooltip.
+ List<String> tip = new ArrayList<>();
+ tip.add(
+ EnumChatFormatting.DARK_PURPLE + "Remaining seed types: "
+ + (this.maxSeedTypes - this.usedSeedTypes));
+ tip.add(
+ EnumChatFormatting.DARK_GREEN + "Remaining seed capacity: "
+ + (this.maxSeedCount - this.usedSeedCount));
+ return tip;
+ })
+ .setSize(18, 18));
+ }
+
+ final int perRow = width / 18;
+ for (int i = 0, imax = ((buttons.size() - 1) / perRow); i <= imax; i++) {
+ DynamicPositionedRow row = new DynamicPositionedRow().setSynced(false);
+ for (int j = 0, jmax = (i == imax ? (buttons.size() - 1) % perRow : (perRow - 1)); j <= jmax; j++) {
+ final int finalI = i * perRow;
+ final int finalJ = j;
+ final int ID = finalI + finalJ;
+ row.widget(buttons.get(ID));
+ }
+ dynamicInventoryWidget.widget(row.setPos(0, i * 18));
+ }
+ dynamicInventoryWidget.setSize(width, height);
+ return dynamicInventoryWidget;
+ }
+
+ @FunctionalInterface
+ public interface TInventoryGetter<T> {
+
+ /**
+ * Allows to get an ItemStack from the dynamic inventory
+ *
+ * @param from Dynamic inventory item from which we want to take an item out
+ * @return ItemStack or null if inaccessible
+ */
+ ItemStack get(T from);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryInjector {
+
+ /**
+ * Allows to insert an item to the dynamic inventory
+ *
+ * @param what ItemStack which we are trying to insert
+ * @return Leftover ItemStack (stackSize == 0 if everything has been inserted) or null
+ */
+ ItemStack inject(ItemStack what);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryExtractor<T> {
+
+ /**
+ * Allows to extract an item from the dynamic inventory
+ *
+ * @return Item that we took out or null
+ */
+ ItemStack extract(T container, EntityPlayerMP player);
+ }
+
+ @FunctionalInterface
+ public interface TInventoryReplacerOrMerger {
+
+ /**
+ * Allows to replace an item in Dynamic Inventory
+ *
+ * @param where which index we want to replace
+ * @param stack what stack we want to replace it with
+ * @return Stack that we are left with or null
+ */
+ ItemStack replaceOrMerge(int where, ItemStack stack);
+ }
+
+}
diff --git a/src/main/java/kubatech/api/IBlockStemAccesor.java b/src/main/java/kubatech/api/IBlockStemAccesor.java
new file mode 100644
index 0000000000..8f2c37e15f
--- /dev/null
+++ b/src/main/java/kubatech/api/IBlockStemAccesor.java
@@ -0,0 +1,8 @@
+package kubatech.api;
+
+import net.minecraft.block.Block;
+
+public interface IBlockStemAccesor {
+
+ Block getCropBlock();
+}
diff --git a/src/main/java/kubatech/api/eig/EIGBucket.java b/src/main/java/kubatech/api/eig/EIGBucket.java
new file mode 100644
index 0000000000..6a3dbdb642
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGBucket.java
@@ -0,0 +1,247 @@
+package kubatech.api.eig;
+
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
+
+import java.util.LinkedList;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+
+import gregtech.api.util.GT_Utility;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public abstract class EIGBucket {
+
+ protected ItemStack seed;
+ protected int seedCount;
+ protected ItemStack[] supportItems;
+
+ public EIGBucket(ItemStack seed, int seedCount, ItemStack[] supportItem) {
+ this.seed = seed.copy();
+ this.seed.stackSize = 1;
+ this.seedCount = seedCount;
+ this.supportItems = supportItem;
+ }
+
+ public EIGBucket(NBTTagCompound nbt) {
+ this.seed = readItemStackFromNBT(nbt.getCompoundTag("seed"));
+ this.seedCount = nbt.getInteger("count");
+
+ // parse support items
+ if (nbt.hasKey("supportItems", 9)) {
+ NBTTagList supportItemsNBTList = nbt.getTagList("supportItems", 10);
+ if (supportItemsNBTList.tagCount() > 0) {
+ this.supportItems = new ItemStack[supportItemsNBTList.tagCount()];
+ for (int i = 0; i < supportItemsNBTList.tagCount(); i++) {
+ this.supportItems[i] = readItemStackFromNBT(supportItemsNBTList.getCompoundTagAt(i));
+ }
+ } else {
+ supportItems = null;
+ }
+ } else {
+ supportItems = null;
+ }
+ }
+
+ /**
+ * Creates a persistent save of the bucket's current data.
+ *
+ * @return The nbt data for this bucket.
+ */
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = new NBTTagCompound();
+ nbt.setString("type", this.getNBTIdentifier());
+ nbt.setTag("seed", writeItemStackToNBT(this.seed));
+ nbt.setInteger("count", this.seedCount);
+ if (this.supportItems != null && this.supportItems.length > 0) {
+ NBTTagList supportItemNBT = new NBTTagList();
+ for (ItemStack supportItem : this.supportItems) {
+ supportItemNBT.appendTag(writeItemStackToNBT(supportItem));
+ }
+ nbt.setTag("supportItems", supportItemNBT);
+ }
+ return nbt;
+ }
+
+ /**
+ * Gets an item stack representing the seeds in this bucket
+ *
+ * @return an item stack representing the seeds in this bucket.
+ */
+ public ItemStack getSeedStack() {
+ ItemStack copied = this.seed.copy();
+ copied.stackSize = this.seedCount;
+ return copied;
+ }
+
+ /**
+ * Gets the number of seeds in this bucket
+ *
+ * @return gets the number of seeds in this bucket.
+ */
+ public int getSeedCount() {
+ return this.seedCount;
+ }
+
+ /**
+ * Gets the display name of the seed in this bucket
+ *
+ * @return The display name of the seed.
+ */
+ public String getDisplayName() {
+ return this.seed.getDisplayName();
+ }
+
+ public String getInfoData() {
+ StringBuilder sb = new StringBuilder();
+ // display invalid buckets, we don't want people to think they lost their seeds or something.
+ sb.append(this.isValid() ? EnumChatFormatting.GREEN : EnumChatFormatting.RED);
+ sb.append("x");
+ sb.append(this.getSeedCount());
+ sb.append(" ");
+ sb.append(this.getDisplayName());
+ this.getAdditionalInfoData(sb);
+ sb.append(EnumChatFormatting.RESET);
+ return sb.toString();
+ }
+
+ protected void getAdditionalInfoData(StringBuilder sb) {}
+
+ /**
+ * Attempts to add seeds to tbe bucket if the input is compatible
+ *
+ * @param input A stack of an item that may be able to be added to our current bucket.
+ * @param maxConsume The maximum amount of seeds to add to this bucket.
+ * @param simulate True if you want to see if you can add more seeds (useful for support item checks)
+ * @return number of seeds consumed, 0 for wrong item, -1 if it missed the support items, -2 if you tried to consume
+ * 0 or less items;
+ */
+ public int tryAddSeed(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input, int maxConsume,
+ boolean simulate) {
+ // Abort is input if empty
+ if (input == null || input.stackSize <= 0) return -2;
+ // Cap max to input count
+ maxConsume = Math.min(maxConsume, input.stackSize);
+ // Abort if item isn't an identical seed.
+ if (!GT_Utility.areStacksEqual(this.seed, input, false)) return 0;
+
+ // no support items, consume and exit early.
+ if (this.supportItems == null || this.supportItems.length <= 0) {
+ if (!simulate) {
+ input.stackSize -= maxConsume;
+ this.seedCount += maxConsume;
+ }
+ return maxConsume;
+ }
+
+ // Check if the item is found
+ LinkedList<ItemStack> toConsumeFrom = new LinkedList<>();
+ supportLoop: for (ItemStack supportItem : this.supportItems) {
+ for (ItemStack otherInput : greenhouse.getStoredInputs()) {
+ // filter usable inputs
+ if (otherInput == null || otherInput.stackSize <= 0) continue;
+ if (!GT_Utility.areStacksEqual(supportItem, otherInput, false)) continue;
+ // update max consume again
+ maxConsume = Math.min(maxConsume, otherInput.stackSize);
+ toConsumeFrom.addLast(otherInput);
+ continue supportLoop;
+ }
+ // no support found, no seeds added
+ return -1;
+ }
+
+ // consume items
+ if (!simulate) {
+ input.stackSize -= maxConsume;
+ for (ItemStack stack : toConsumeFrom) {
+ stack.stackSize -= maxConsume;
+ }
+ this.seedCount += maxConsume;
+ }
+ return maxConsume;
+ }
+
+ /**
+ * Attempts to remove a seed from the bucket
+ *
+ * @param toRemove The maximum amount of items to remove.
+ * @return The items that were removed from the bucket. Null if the bucket is empty.
+ */
+ public ItemStack[] tryRemoveSeed(int toRemove, boolean simulate) {
+ // validate inputs
+ toRemove = Math.min(this.seedCount, toRemove);
+ if (toRemove <= 0) return null;
+
+ // consume and return output
+ ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)];
+ ret[0] = this.seed.copy();
+ ret[0].stackSize = toRemove;
+ if (this.supportItems != null) {
+ for (int i = 0; i < this.supportItems.length; i++) {
+ ret[i + 1] = this.supportItems[i].copy();
+ ret[i + 1].stackSize = toRemove;
+ }
+ }
+ if (!simulate) {
+ this.seedCount -= toRemove;
+ }
+ return ret;
+ }
+
+ /**
+ * Sets the seed count to 0 and returns item stacks representing every item in this bucket.
+ *
+ * @return The contents of the bucket
+ */
+ public ItemStack[] emptyBucket() {
+ if (this.seedCount <= 0) return null;
+ ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)];
+ ret[0] = this.seed.copy();
+ ret[0].stackSize = this.seedCount;
+ if (this.supportItems != null) {
+ for (int i = 0; i < this.supportItems.length; i++) {
+ ret[i + 1] = this.supportItems[i].copy();
+ ret[i + 1].stackSize = this.seedCount;
+ }
+ }
+ this.seedCount = 0;
+ return ret;
+ }
+
+ /**
+ * Returns true if the bucket can output items.
+ *
+ * @return true if the bucket is valid.
+ */
+ public boolean isValid() {
+ return this.seed != null && this.seedCount > 0;
+ }
+
+ /**
+ * Gets the identifier used to identify this class during reconstruction
+ *
+ * @return the identifier for this bucket type.
+ */
+ protected abstract String getNBTIdentifier();
+
+ /**
+ * Adds item drops to the item tracker.
+ *
+ * @param multiplier A multiplier to apply to the output.
+ * @param tracker The item drop tracker
+ */
+ public abstract void addProgress(double multiplier, EIGDropTable tracker);
+
+ /**
+ * Attempts to revalidate a seed bucket. If it returns false, attempt to seed and support items and delete the
+ * bucket.
+ *
+ * @param greenhouse The greenhouse that contains the bucket.
+ * @return True if the bucket was successfully validated. {@link EIGBucket#isValid()} should also return true.
+ */
+ public abstract boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse);
+
+}
diff --git a/src/main/java/kubatech/api/eig/EIGDropTable.java b/src/main/java/kubatech/api/eig/EIGDropTable.java
new file mode 100644
index 0000000000..bb5bbe6456
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGDropTable.java
@@ -0,0 +1,224 @@
+package kubatech.api.eig;
+
+import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
+import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
+
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+
+import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
+
+public class EIGDropTable {
+
+ private static final String NBT_DROP_TABLE_ITEM_KEY = "item";
+ private static final String NBT_DROP_TABLE_COUNT_KEY = "count";
+
+ private final ItemStackMap<Double> dropTable;
+
+ /**
+ * Initialises a new empty drop table.
+ */
+ public EIGDropTable() {
+ this.dropTable = new ItemStackMap<>(true);
+ }
+
+ /**
+ * Loads a serialised drop table from nbt.
+ *
+ * @param nbt The nbt tag that contains the key for a drop table
+ * @param key The name of the key name for the drop table.
+ */
+ public EIGDropTable(NBTTagCompound nbt, String key) {
+ // should create an empty table if no drops are found.
+ this(nbt.getTagList(key, 10));
+ }
+
+ /**
+ * Loads a serialised drop table from nbt.
+ *
+ * @param nbt The nbt tag that contains the key for a drop table
+ */
+ public EIGDropTable(NBTTagList nbt) {
+ this();
+ for (int i = 0; i < nbt.tagCount(); i++) {
+ NBTTagCompound drop = nbt.getCompoundTagAt(i);
+ dropTable.merge(
+ readItemStackFromNBT(drop.getCompoundTag(NBT_DROP_TABLE_ITEM_KEY)),
+ drop.getDouble(NBT_DROP_TABLE_COUNT_KEY),
+ Double::sum);
+ }
+ }
+
+ /**
+ * Serialises the drop table to nbt
+ *
+ * @return The serialised drop table.
+ */
+ public NBTTagList save() {
+ NBTTagList nbt = new NBTTagList();
+ for (Map.Entry<ItemStack, Double> entry : this.dropTable.entrySet()) {
+ NBTTagCompound entryNBT = new NBTTagCompound();
+ entryNBT.setTag(NBT_DROP_TABLE_ITEM_KEY, writeItemStackToNBT(entry.getKey()));
+ entryNBT.setDouble(NBT_DROP_TABLE_COUNT_KEY, entry.getValue());
+ nbt.appendTag(entryNBT);
+ }
+ return nbt;
+ }
+
+ /**
+ * Adds a drop to the drop table
+ *
+ * @param itemStack The item to add to the table.
+ * @param amount The amount to add to the table.
+ */
+ public void addDrop(ItemStack itemStack, double amount) {
+ ItemStack key = itemStack.copy();
+ key.stackSize = 1;
+ this.dropTable.merge(key, amount, Double::sum);
+ }
+
+ /**
+ * Adds the values from this drop table to another, but multiplies the amount by a random amount bound by variance.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz
+ * @param rand The random source for the variance.
+ */
+ public void addTo(EIGDropTable target, double variance, Random rand) {
+ this.addTo(target, 1.0, variance, rand);
+ }
+
+ /**
+ * Adds the values from this drop table to another, but multiplies the amount by a multiplier and a random amount
+ * bound by variance.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param multiplier A multiplier to apply to all amounts from this drop table.
+ * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz.
+ * @param rand The random source for the variance.
+ */
+ public void addTo(EIGDropTable target, double multiplier, double variance, Random rand) {
+ this.addTo(target, variance * (rand.nextDouble() - 0.5) * multiplier);
+ }
+
+ /**
+ * Adds the values from this drop table to another.
+ *
+ * @param target The drop table that you want to add the value to.
+ */
+ public void addTo(EIGDropTable target) {
+ this.addTo(target, 1.0);
+ }
+
+ /**
+ * Adds the values from this drop table to another but multiplies the values by a multiplier.
+ *
+ * @param target The drop table that you want to add the value to.
+ * @param multiplier A multiplier to apply to all amounts from this drop table.
+ */
+ public void addTo(EIGDropTable target, double multiplier) {
+ for (Map.Entry<ItemStack, Double> entry : this.dropTable.entrySet()) {
+ target.dropTable.merge(entry.getKey(), entry.getValue() * multiplier, Double::sum);
+ }
+ }
+
+ /**
+ * Checks if the drop table is empty;
+ *
+ * @return true if empty.
+ */
+ public boolean isEmpty() {
+ return this.dropTable.isEmpty();
+ }
+
+ /**
+ * Returns the entry set for this drop table.
+ *
+ * @return ItemStack -> amount
+ */
+ public Set<Map.Entry<ItemStack, Double>> entrySet() {
+ return this.dropTable.entrySet();
+ }
+
+ /**
+ * Gets the amount for a specific item.
+ *
+ * @param item The item to look for.
+ * @return 0 if nothing is found else a positive value.
+ */
+ public double getItemAmount(ItemStack item) {
+ if (this.dropTable.containsKey(item)) {
+ return this.dropTable.get(item);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the amount for a specific item.
+ *
+ * @param item The item to look for.
+ */
+ public void setItemAmount(ItemStack item, double value) {
+ this.dropTable.put(item, value);
+ }
+
+ /**
+ * Removes an item from the drop table
+ *
+ * @param item The item to remove from the drop table.
+ */
+ public void removeItem(ItemStack item) {
+ this.dropTable.remove(item);
+ }
+
+ /**
+ * Creates a new drop table that is the intersection of this drop table and another.
+ *
+ *
+ * @param with The drop table to intersect with.
+ * @return The result of the intersection.
+ */
+ public EIGDropTable intersect(EIGDropTable with) {
+ EIGDropTable ret = new EIGDropTable();
+ for (ItemStack key : with.dropTable.keySet()) {
+ if (this.dropTable.containsKey(key)) {
+ ret.addDrop(key, this.dropTable.get(key));
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Consumes drops with drop counts above 1 and returns a list of the consumed item stacks.
+ *
+ * @return The list of consumed items;
+ */
+ public ItemStack[] getDrops() {
+ // doesn't need to filter for less than 0 so that the EIG displays the progress of incomplete items.
+ return this.dropTable.entrySet()
+ .parallelStream()
+ .map(EIGDropTable::computeDrops)
+ .toArray(ItemStack[]::new);
+ }
+
+ /**
+ * Consumes the items in the entry and returns the consumed item without removing partial items.
+ *
+ * @param entry The entry to consume from
+ * @return The item tha twas removed.
+ */
+ private static ItemStack computeDrops(Map.Entry<ItemStack, Double> entry) {
+ ItemStack copied = entry.getKey()
+ .copy();
+ copied.stackSize = (int) Math.floor(entry.getValue());
+ if (entry.getValue() >= 1.0d) {
+ entry.setValue(entry.getValue() % 1);
+ }
+ return copied;
+ }
+}
diff --git a/src/main/java/kubatech/api/eig/EIGMode.java b/src/main/java/kubatech/api/eig/EIGMode.java
new file mode 100644
index 0000000000..68ad633773
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/EIGMode.java
@@ -0,0 +1,154 @@
+package kubatech.api.eig;
+
+import static kubatech.kubatech.error;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public abstract class EIGMode {
+
+ public abstract int getUIIndex();
+
+ public abstract String getName();
+
+ public abstract int getMinVoltageTier();
+
+ public abstract int getMinGlassTier();
+
+ public abstract int getStartingSlotCount();
+
+ public abstract int getSlotPerTierMultiplier();
+
+ public abstract int getSlotCount(int machineTier);
+
+ public abstract int getSeedCapacityPerSlot();
+
+ public abstract int getWeedEXMultiplier();
+
+ public abstract int getMaxFertilizerUsagePerSeed();
+
+ public abstract double getFertilizerBoost();
+
+ public abstract GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder);
+
+ /**
+ * Used to resolve factory type to an identifier.
+ */
+ private final HashMap<String, IEIGBucketFactory> factories;
+ /**
+ * A way to have other mods submit custom buckets that can be prioritized over our default buckets
+ */
+ private final LinkedList<IEIGBucketFactory> orderedFactories;
+
+ public EIGMode() {
+ this.factories = new HashMap<>();
+ this.orderedFactories = new LinkedList<>();
+ }
+
+ /**
+ * Adds a bucket factory to the EIG mode and gives it a low priority. Factories with using existing IDs will
+ * overwrite each other.
+ *
+ * @param factory The bucket factory to add.
+ */
+ public void addLowPriorityFactory(IEIGBucketFactory factory) {
+ String factoryId = factory.getNBTIdentifier();
+ dealWithDuplicateFactoryId(factoryId);
+ // add factory as lowest priority
+ this.factories.put(factoryId, factory);
+ this.orderedFactories.addLast(factory);
+ }
+
+ /**
+ * Adds a bucket factory to the EIG mode and gives it a high priority. Factories with using existing IDs will
+ * overwrite each other.
+ *
+ * @param factory The bucket factory to add.
+ */
+ public void addHighPriorityFactory(IEIGBucketFactory factory) {
+ String factoryId = factory.getNBTIdentifier();
+ dealWithDuplicateFactoryId(factoryId);
+ // add factory as lowest priority
+ this.factories.put(factoryId, factory);
+ this.orderedFactories.addFirst(factory);
+ }
+
+ /**
+ * A standardized way to deal with duplicate factory type identifiers.
+ *
+ * @param factoryId The ID of the factory
+ */
+ private void dealWithDuplicateFactoryId(String factoryId) {
+ if (this.factories.containsKey(factoryId)) {
+ // TODO: Check with devs to see if they want a throw instead.
+ error("Duplicate EIG bucket index detected!!!: " + factoryId);
+ // remove duplicate from ordered list
+ this.orderedFactories.remove(this.factories.get(factoryId));
+ }
+ }
+
+ /**
+ * Attempts to create a new bucket from a given item. Returns if the item cannot be inserted into the EIG.
+ *
+ * @see IEIGBucketFactory#tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse, ItemStack)
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that will contain the seed.
+ * @param input The {@link ItemStack} for the input item.
+ * @param maxConsume The maximum amount of items to consume.
+ * @param simulate Whether to actually consume the seed.
+ * @return Null if no bucket could be created from the item.
+ */
+ public EIGBucket tryCreateNewBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input,
+ int maxConsume, boolean simulate) {
+ // Validate inputs
+ if (input == null) return null;
+ maxConsume = Math.min(input.stackSize, maxConsume);
+ if (maxConsume <= 0) return null;
+ for (IEIGBucketFactory factory : this.orderedFactories) {
+ EIGBucket bucket = factory.tryCreateBucket(greenhouse, input);
+ if (bucket == null || !bucket.isValid()) continue;
+ if (!simulate) input.stackSize--;
+ maxConsume--;
+ bucket.tryAddSeed(greenhouse, input, maxConsume, simulate);
+ return bucket;
+ }
+ return null;
+ }
+
+ /**
+ * Restores the buckets of an EIG for the given mode.
+ *
+ * @see IEIGBucketFactory#restore(NBTTagCompound)
+ * @param bucketNBTList The
+ */
+ public void restoreBuckets(NBTTagList bucketNBTList, List<EIGBucket> loadTo) {
+ for (int i = 0; i < bucketNBTList.tagCount(); i++) {
+ // validate nbt
+ NBTTagCompound bucketNBT = bucketNBTList.getCompoundTagAt(i);
+ if (bucketNBT.hasNoTags()) {
+ error("Empty nbt bucket found in EIG nbt.");
+ continue;
+ }
+ if (!bucketNBT.hasKey("type", 8)) {
+ error("Failed to identify bucket type in EIG nbt.");
+ continue;
+ }
+ // identify bucket type
+ String bucketType = bucketNBT.getString("type");
+ IEIGBucketFactory factory = factories.getOrDefault(bucketType, null);
+ if (factory == null) {
+ error("failed to find EIG bucket factory for type: " + bucketType);
+ continue;
+ }
+ // restore bucket
+ loadTo.add(factory.restore(bucketNBT));
+ }
+ }
+}
diff --git a/src/main/java/kubatech/api/eig/IEIGBucketFactory.java b/src/main/java/kubatech/api/eig/IEIGBucketFactory.java
new file mode 100644
index 0000000000..647e544573
--- /dev/null
+++ b/src/main/java/kubatech/api/eig/IEIGBucketFactory.java
@@ -0,0 +1,15 @@
+package kubatech.api.eig;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public interface IEIGBucketFactory {
+
+ String getNBTIdentifier();
+
+ EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack stack);
+
+ EIGBucket restore(NBTTagCompound nbt);
+}
diff --git a/src/main/java/kubatech/api/enums/EIGModes.java b/src/main/java/kubatech/api/enums/EIGModes.java
new file mode 100644
index 0000000000..a81de2b8c5
--- /dev/null
+++ b/src/main/java/kubatech/api/enums/EIGModes.java
@@ -0,0 +1,42 @@
+package kubatech.api.enums;
+
+import java.util.HashMap;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.eigmodes.EIGIC2Mode;
+import kubatech.tileentity.gregtech.multiblock.eigmodes.EIGNormalMode;
+
+public class EIGModes {
+
+ private static final HashMap<String, EIGMode> modes = new HashMap<>();
+
+ public static final EIGMode Normal = addMode(EIGNormalMode.instance);
+ public static final EIGMode IC2 = addMode(EIGIC2Mode.instance);
+
+ // this is basically a fake enum, plz don't instantiate
+ private EIGModes() {}
+
+ private static EIGMode addMode(EIGMode mode) {
+ modes.put(mode.getName(), mode);
+ return mode;
+ }
+
+ public static EIGMode getModeFromName(String name) {
+ return modes.get(name);
+ }
+
+ public static EIGMode getNextMode(EIGMode from) {
+ int id = (from.getUIIndex() + 1) % modes.size();
+ for (EIGMode mode : modes.values()) {
+ if (mode.getUIIndex() == id) return mode;
+ }
+ return Normal;
+ }
+
+ public static void addTooltipInfo(GT_Multiblock_Tooltip_Builder tt) {
+ // maybe make this use the mods list instead
+ EIGModes.Normal.addTooltipInfo(tt);
+ EIGModes.IC2.addTooltipInfo(tt);
+ }
+}
diff --git a/src/main/java/kubatech/api/enums/EIGSetupPhase.java b/src/main/java/kubatech/api/enums/EIGSetupPhase.java
new file mode 100644
index 0000000000..95e8854347
--- /dev/null
+++ b/src/main/java/kubatech/api/enums/EIGSetupPhase.java
@@ -0,0 +1,16 @@
+package kubatech.api.enums;
+
+public enum EIGSetupPhase {
+
+ Operation(0, "Operation"),
+ Input(1, "Input"),
+ Output(2, "Output");
+
+ public final int id;
+ public final String name;
+
+ private EIGSetupPhase(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+}
diff --git a/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java b/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java
new file mode 100644
index 0000000000..313610de07
--- /dev/null
+++ b/src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java
@@ -0,0 +1,72 @@
+package kubatech.api.gui;
+
+import java.util.Collections;
+
+import com.gtnewhorizons.modularui.api.NumberFormatMUI;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.TextRenderer;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.common.internal.Theme;
+
+public class AutoScalingStackSizeText implements IDrawable {
+
+ private static final TextRenderer measuringRenderer = new TextRenderer();
+ private static final NumberFormatMUI muiNumberFormat = new NumberFormatMUI();
+ private static final TextRenderer renderer = new TextRenderer();
+ private Alignment alignment = Alignment.Center;
+ private final String text;
+ private int simWidth;
+
+ private int color;
+ private boolean shadow = false;
+
+ public AutoScalingStackSizeText(long stackSize) {
+ this.text = muiNumberFormat.formatWithSuffix(stackSize);
+ this.color = Theme.INSTANCE.getText();
+ this.measure();
+ }
+
+ public AutoScalingStackSizeText color(int color) {
+ this.color = Color.withAlpha(color, 255);
+ return this;
+ }
+
+ public AutoScalingStackSizeText shadow(boolean shadow) {
+ this.shadow = shadow;
+ return this;
+ }
+
+ public AutoScalingStackSizeText shadow() {
+ return shadow(true);
+ }
+
+ public AutoScalingStackSizeText alignment(Alignment alignment) {
+ this.alignment = alignment;
+ return this;
+ }
+
+ public AutoScalingStackSizeText measure() {
+ this.simWidth = measuringRenderer.getMaxWidth(Collections.singletonList(this.text));
+ return this;
+ }
+
+ public boolean hasColor() {
+ return Color.getAlpha(color) > 0;
+ }
+
+ @Override
+ public void applyThemeColor(int color) {
+ renderer.setColor(hasColor() ? this.color : Theme.INSTANCE.getText());
+ }
+
+ @Override
+ public void draw(float x, float y, float width, float height, float partialTicks) {
+ renderer.setPos((int) (x - 0.5), (int) (y - 0.5));
+ renderer.setShadow(this.shadow);
+ renderer.setAlignment(alignment, width, height);
+ renderer.setColor(this.color);
+ renderer.setScale(this.simWidth <= 16.0f ? 1.0f : 16.0f / this.simWidth);
+ renderer.draw(this.text);
+ }
+}
diff --git a/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java b/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java
index bf74df9154..4451e3f401 100644
--- a/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java
+++ b/src/main/java/kubatech/api/implementations/KubaTechGTMultiBlockBase.java
@@ -208,7 +208,7 @@ public abstract class KubaTechGTMultiBlockBase<T extends GT_MetaTileEntity_Exten
return tryOutputAll(list, l -> Collections.singletonList((ItemStack) l));
}
- protected boolean tryOutputAll(List<?> list, Function<Object, List<ItemStack>> mappingFunction) {
+ protected <Y> boolean tryOutputAll(List<Y> list, Function<Y, List<ItemStack>> mappingFunction) {
if (list == null || list.isEmpty() || mappingFunction == null) return false;
int emptySlots = 0;
boolean ignoreEmptiness = false;
@@ -224,6 +224,10 @@ public abstract class KubaTechGTMultiBlockBase<T extends GT_MetaTileEntity_Exten
boolean wasSomethingRemoved = false;
while (!list.isEmpty()) {
List<ItemStack> toOutputNow = mappingFunction.apply(list.get(0));
+ if (toOutputNow == null) {
+ list.remove(0);
+ continue;
+ }
if (!ignoreEmptiness && emptySlots < toOutputNow.size()) break;
emptySlots -= toOutputNow.size();
list.remove(0);
diff --git a/src/main/java/kubatech/api/utils/StringUtils.java b/src/main/java/kubatech/api/utils/StringUtils.java
index c60da71b14..68f6c8249f 100644
--- a/src/main/java/kubatech/api/utils/StringUtils.java
+++ b/src/main/java/kubatech/api/utils/StringUtils.java
@@ -22,6 +22,8 @@ package kubatech.api.utils;
import net.minecraft.util.EnumChatFormatting;
+import gregtech.api.enums.GT_Values;
+
public class StringUtils {
private static final String[] rainbow = new String[] { EnumChatFormatting.DARK_RED.toString(),
@@ -49,4 +51,8 @@ public class StringUtils {
public static String applyRainbow(String str) {
return applyRainbow(str, 0, "");
}
+
+ public static String voltageTooltipFormatted(int tier) {
+ return GT_Values.TIER_COLORS[tier] + GT_Values.VN[tier] + EnumChatFormatting.GRAY;
+ }
}
diff --git a/src/main/java/kubatech/loaders/EIGBucketLoader.java b/src/main/java/kubatech/loaders/EIGBucketLoader.java
new file mode 100644
index 0000000000..4dbf83112b
--- /dev/null
+++ b/src/main/java/kubatech/loaders/EIGBucketLoader.java
@@ -0,0 +1,27 @@
+package kubatech.loaders;
+
+import static gregtech.api.enums.Mods.ThaumicBases;
+
+import kubatech.api.enums.EIGModes;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGFlowerBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGRainbowCactusBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGSeedBucket;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGStemBucket;
+
+public class EIGBucketLoader {
+
+ public static void LoadEIGBuckets() {
+ // IC2 buckets
+ EIGModes.IC2.addLowPriorityFactory(EIGIC2Bucket.factory);
+
+ // Regular Mode Buckets
+ if (ThaumicBases.isModLoaded()) {
+ EIGModes.Normal.addLowPriorityFactory(EIGRainbowCactusBucket.factory);
+ }
+ EIGModes.Normal.addLowPriorityFactory(EIGFlowerBucket.factory);
+ EIGModes.Normal.addLowPriorityFactory(EIGStemBucket.factory);
+ EIGModes.Normal.addLowPriorityFactory(EIGSeedBucket.factory);
+ }
+
+}
diff --git a/src/main/java/kubatech/loaders/RecipeLoader.java b/src/main/java/kubatech/loaders/RecipeLoader.java
index 7d609021b6..3ebba3fea6 100644
--- a/src/main/java/kubatech/loaders/RecipeLoader.java
+++ b/src/main/java/kubatech/loaders/RecipeLoader.java
@@ -76,6 +76,7 @@ import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.FluidRegistry;
import cpw.mods.fml.common.registry.GameRegistry;
+import forestry.plugins.PluginCore;
import gregtech.api.enums.GT_Values;
import gregtech.api.enums.Materials;
import gregtech.api.enums.OrePrefixes;
@@ -83,6 +84,7 @@ import gregtech.api.metatileentity.MetaTileEntity;
import gregtech.api.util.GT_ModHandler;
import gregtech.api.util.GT_Utility;
import gtPlusPlus.core.lib.CORE;
+import ic2.core.Ic2Items;
import kubatech.api.enums.ItemList;
import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_DEFusionCrafter;
import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeEntityCrusher;
@@ -154,6 +156,15 @@ public class RecipeLoader {
? GT_ModHandler.getModItem(GregTech.ID, "gt.blockmachines", 1, 11104) // IV World Accelerator
: gregtech.api.enums.ItemList.Robot_Arm_IV,
'Z', OrePrefixes.circuit.get(Materials.Ultimate) });
+
+ // Vanilla should always be loaded
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse.addFertilizerItem(new ItemStack(Items.dye, 1, 15));
+ // IC2 should always be loaded
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse.addFertilizerItem(Ic2Items.fertilizer);
+ if (Forestry.isModLoaded()) {
+ GT_MetaTileEntity_ExtremeIndustrialGreenhouse
+ .addFertilizerItem(PluginCore.items.fertilizerCompound.getItemStack(1));
+ }
}
if (registerMTEUsingID(
5_001,
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java
index cdab236542..a45e66142b 100644
--- a/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/GT_MetaTileEntity_ExtremeIndustrialGreenhouse.java
@@ -24,6 +24,7 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlock;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.onElementPass;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.transpose;
+import static gregtech.api.enums.Mods.BartWorks;
import static gregtech.api.enums.Mods.ProjectRedIllumination;
import static gregtech.api.enums.Mods.RandomThings;
import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER;
@@ -31,45 +32,36 @@ import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_
import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_ACTIVE_GLOW;
import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_DISTILLATION_TOWER_GLOW;
import static gregtech.api.util.GT_StructureUtility.ofHatchAdder;
+import static gregtech.api.util.GT_Utility.filterValidMTEs;
import static kubatech.api.Variables.Author;
import static kubatech.api.Variables.StructureHologram;
import static kubatech.api.utils.ItemUtils.readItemStackFromNBT;
-import static kubatech.api.utils.ItemUtils.writeItemStackToNBT;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Random;
+import java.util.stream.Collectors;
import net.minecraft.block.Block;
-import net.minecraft.block.BlockFlower;
-import net.minecraft.block.BlockStem;
import net.minecraft.client.Minecraft;
+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.InventoryCrafting;
import net.minecraft.inventory.Slot;
-import net.minecraft.item.Item;
-import net.minecraft.item.ItemBlock;
-import net.minecraft.item.ItemSeedFood;
-import net.minecraft.item.ItemSeeds;
import net.minecraft.item.ItemStack;
-import net.minecraft.item.crafting.CraftingManager;
-import net.minecraft.item.crafting.IRecipe;
import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.tileentity.TileEntity;
+import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.World;
-import net.minecraftforge.common.IPlantable;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
@@ -83,7 +75,6 @@ import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import com.gtnewhorizon.structurelib.structure.StructureDefinition;
import com.gtnewhorizons.modularui.api.ModularUITextures;
import com.gtnewhorizons.modularui.api.drawable.Text;
-import com.gtnewhorizons.modularui.api.drawable.shapes.Rectangle;
import com.gtnewhorizons.modularui.api.math.Color;
import com.gtnewhorizons.modularui.api.math.MainAxisAlignment;
import com.gtnewhorizons.modularui.api.screen.ModularUIContext;
@@ -101,12 +92,11 @@ import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
import com.gtnewhorizons.modularui.common.widget.SlotWidget;
import com.gtnewhorizons.modularui.common.widget.TextWidget;
-import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import gregtech.api.GregTech_API;
+import gregtech.api.enums.GTVoltageIndex;
import gregtech.api.enums.GT_Values;
-import gregtech.api.enums.ItemList;
import gregtech.api.enums.Materials;
import gregtech.api.enums.Textures;
import gregtech.api.gui.modularui.GT_UITextures;
@@ -114,28 +104,26 @@ import gregtech.api.interfaces.ITexture;
import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
-import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Input;
-import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_MultiInput;
+import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_OutputBus;
import gregtech.api.recipe.check.CheckRecipeResult;
import gregtech.api.recipe.check.CheckRecipeResultRegistry;
import gregtech.api.recipe.check.SimpleCheckRecipeResult;
import gregtech.api.render.TextureFactory;
import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
import gregtech.api.util.GT_Utility;
-import gregtech.common.GT_DummyWorld;
-import gregtech.common.blocks.GT_Block_Ores_Abstract;
-import gregtech.common.blocks.GT_Item_Ores;
-import gregtech.common.blocks.GT_TileEntity_Ores;
-import ic2.api.crops.CropCard;
-import ic2.api.crops.Crops;
-import ic2.core.Ic2Items;
-import ic2.core.crop.TileEntityCrop;
+import gregtech.api.util.VoidProtectionHelper;
+import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_OutputBus_ME;
import ic2.core.init.BlocksItems;
import ic2.core.init.InternalName;
import kubatech.Tags;
-import kubatech.api.DynamicInventory;
+import kubatech.api.EIGDynamicInventory;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.EIGMode;
+import kubatech.api.enums.EIGModes;
import kubatech.api.implementations.KubaTechGTMultiBlockBase;
import kubatech.client.effect.CropRenderer;
+import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket;
@SuppressWarnings("unused")
public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
@@ -159,36 +147,69 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
* Changing T one number down will buff the EIG twice, as well as changing it up will nerf the EIG twice
* (That is because accelerators are imperfectly scaled in game LV = 2x, MV = 4x, ...)
*/
- public static final int EIG_BALANCE_IC2_ACCELERATOR_TIER = 5; // IV
-
- /***
- * All crops above this threshold will die if WEED-EX 9000 isn't supplied.
- * Consider changing this value after making changes to the EIG_BALANCE_IC2_ACCELERATOR_TIER.
- */
- private static final int EIG_CROP_LIMIT_FOR_WEEDEX9000_REQUIREMENT = 1000;
+ public static final int EIG_BALANCE_IC2_ACCELERATOR_TIER = GTVoltageIndex.IV;
+ public static final int EIG_BALANCE_REGULAR_MODE_MIN_TIER = GTVoltageIndex.EV;
+ public static final int EIG_BALANCE_IC2_MODE_MIN_TIER = EIG_BALANCE_IC2_ACCELERATOR_TIER + 1;
+ public static final double EIG_BALANCE_MAX_FERTILIZER_BOOST = 4.0d;
+ public static final int EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT = 1000;
+ public static final int EIG_BALANCE_WATER_USAGE_PER_SEED = 1000;
+
+ private static final Fluid WEEDEX_FLUID = Materials.WeedEX9000.mFluid;
+ private static final LinkedList<ItemStack> FERTILIZER_ITEM_LIST = new LinkedList<>();
+
+ public static void addFertilizerItem(ItemStack fertilizer) {
+ FERTILIZER_ITEM_LIST.addLast(fertilizer);
+ }
private static final boolean debug = false;
/***
* Changing this variable will cause ALL EIGs in the world to regenerate their drop tables.
*/
- private static final int EIG_MATH_VERSION = 0;
+ private static final int NBT_REVISION = 1;
private static final int CONFIGURATION_WINDOW_ID = 999;
- public final List<GreenHouseSlot> mStorage = new ArrayList<>();
- private int oldVersion = 0;
+ public final List<EIGBucket> buckets = new LinkedList<>();
+ public final EIGDropTable dropTracker = new EIGDropTable();
+ public Collection<EIGMigrationHolder> toMigrate;
+ public EIGDropTable guiDropTracker = new EIGDropTable();
+ private HashMap<ItemStack, Double> synchedGUIDropTracker = new HashMap<>();
+ private int maxSeedTypes = 0;
+ private int maxSeedCount = 0;
+ /**
+ * The setup phase of the EIG. 0 operation. 1 input. 2 output.
+ */
+ private int setupPhase = 1;
+ /**
+ * The amount of water used per cycle.
+ */
+ private int waterUsage = 0;
+ /**
+ * The tier of the glass on the EIG.
+ */
+ private byte glassTier = 0;
+ /**
+ * The Amount of Weed-EX used per cycle.
+ */
+ private int weedEXUsage = 0;
+ /**
+ * The mode that the EIG is in.
+ */
+ private EIGMode mode = EIGModes.Normal;
+ /**
+ * Determines whether new IC2 buckets will use no humidity for their growth speed calculation.
+ */
+ private boolean useNoHumidity = false;
+
+ public boolean isInNoHumidityMode() {
+ return this.useNoHumidity;
+ }
+
+ // region structure stuff
+
private int mCasing = 0;
- public int mMaxSlots = 0;
- private int setupphase = 1;
- private boolean isIC2Mode = false;
- private byte glasTier = 0;
- private int waterusage = 0;
- private int weedexusage = 0;
- private boolean isNoHumidity = false;
private static final int CASING_INDEX = 49;
private static final String STRUCTURE_PIECE_MAIN = "main";
- private static final Item forestryfertilizer = GameRegistry.findItem("Forestry", "fertilizerCompound");
- private static final Fluid weedex = Materials.WeedEX9000.mFluid;
private static final IStructureDefinition<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> STRUCTURE_DEFINITION = StructureDefinition
.<GT_MetaTileEntity_ExtremeIndustrialGreenhouse>builder()
.addShape(
@@ -220,12 +241,16 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
.addElement(
'l',
ProjectRedIllumination.isModLoaded()
- ? ofBlock(Block.getBlockFromName("ProjRed|Illumination:projectred.illumination.lamp"), 10)
+ ? ofChain(
+ ofBlock(Block.getBlockFromName("ProjRed|Illumination:projectred.illumination.lamp"), 10),
+ ofBlock(Block.getBlockFromName("ProjRed|Illumination:projectred.illumination.lamp"), 26))
: ofChain(ofBlock(Blocks.redstone_lamp, 0), ofBlock(Blocks.lit_redstone_lamp, 0)))
.addElement(
'g',
- BorosilicateGlass
- .ofBoroGlass((byte) 0, (byte) 1, Byte.MAX_VALUE, (te, t) -> te.glasTier = t, te -> te.glasTier))
+ BartWorks.isModLoaded()
+ ? BorosilicateGlass
+ .ofBoroGlass((byte) 0, (byte) 1, Byte.MAX_VALUE, (te, t) -> te.glassTier = t, te -> te.glassTier)
+ : onElementPass(t -> t.glassTier = 100, ofBlock(Blocks.glass, 0)))
.addElement(
'd',
ofBlock(
@@ -237,68 +262,33 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
ofChain(ofBlock(Blocks.water, 0), ofBlock(BlocksItems.getFluidBlock(InternalName.fluidDistilledWater), 0)))
.build();
- public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(int aID, String aName, String aNameRegional) {
- super(aID, aName, aNameRegional);
- }
-
- public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(String aName) {
- super(aName);
- }
-
@Override
- public void onRemoval() {
- super.onRemoval();
- if (getBaseMetaTileEntity().isServerSide()) tryOutputAll(mStorage, s -> {
- ArrayList<ItemStack> l = new ArrayList<>(2);
- l.add(((GreenHouseSlot) s).input.copy());
- if (((GreenHouseSlot) s).undercrop != null) l.add(((GreenHouseSlot) s).undercrop.copy());
- return l;
- });
+ public IStructureDefinition<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> getStructureDefinition() {
+ return STRUCTURE_DEFINITION;
}
@Override
- public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ) {
- if (aPlayer.isSneaking()) {
- if (this.mMaxProgresstime > 0) {
- GT_Utility.sendChatToPlayer(aPlayer, "You can't change IC2 mode if the machine is working!");
- return;
- }
- if (!mStorage.isEmpty()) {
- GT_Utility.sendChatToPlayer(aPlayer, "You can't change IC2 mode if there are seeds inside!");
- return;
- }
- this.isIC2Mode = !this.isIC2Mode;
- GT_Utility.sendChatToPlayer(aPlayer, "IC2 mode is now " + (this.isIC2Mode ? "enabled" : "disabled."));
- } else {
- if (this.mMaxProgresstime > 0) {
- GT_Utility.sendChatToPlayer(aPlayer, "You can't enable/disable setup if the machine is working!");
- return;
- }
- this.setupphase++;
- if (this.setupphase == 3) this.setupphase = 0;
- GT_Utility.sendChatToPlayer(
- aPlayer,
- "EIG is now running in " + (this.setupphase == 1 ? "setup mode (input)."
- : (this.setupphase == 2 ? "setup mode (output)." : "normal operation.")));
- }
- }
+ public boolean checkMachine(IGregTechTileEntity iGregTechTileEntity, ItemStack itemStack) {
+ mCasing = 0;
+ glassTier = 0;
+ if (debug) glassTier = 8;
- @Override
- public boolean onWireCutterRightClick(ForgeDirection side, ForgeDirection wrenchingSide, EntityPlayer aPlayer,
- float aX, float aY, float aZ) {
- isNoHumidity = !isNoHumidity;
- GT_Utility.sendChatToPlayer(aPlayer, "Give incoming crops no humidity " + isNoHumidity);
- return true;
- }
+ if (!checkPiece(STRUCTURE_PIECE_MAIN, 2, 5, 0)) return false;
- @Override
- public IMetaTileEntity newMetaEntity(IGregTechTileEntity iGregTechTileEntity) {
- return new GT_MetaTileEntity_ExtremeIndustrialGreenhouse(this.mName);
+ if (this.glassTier < 8 && !this.mEnergyHatches.isEmpty())
+ for (GT_MetaTileEntity_Hatch_Energy hatchEnergy : this.mEnergyHatches)
+ if (this.glassTier < hatchEnergy.mTier) return false;
+
+ boolean valid = this.mMaintenanceHatches.size() == 1 && !this.mEnergyHatches.isEmpty() && this.mCasing >= 70;
+
+ if (valid) this.updateSeedLimits();
+
+ return valid;
}
@Override
- public IStructureDefinition<GT_MetaTileEntity_ExtremeIndustrialGreenhouse> getStructureDefinition() {
- return STRUCTURE_DEFINITION;
+ public void construct(ItemStack itemStack, boolean b) {
+ buildPiece(STRUCTURE_PIECE_MAIN, itemStack, b, 2, 5, 0);
}
@Override
@@ -306,51 +296,37 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
return (d, r, f) -> d.offsetY == 0 && r.isNotRotated() && f.isNotFlipped();
}
+ // endregion structure stuff
+
+ // region tooltip
+
@Override
protected GT_Multiblock_Tooltip_Builder createTooltip() {
GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder();
+ String fertilizerBoostMax = String.format("%.0f", EIG_BALANCE_MAX_FERTILIZER_BOOST * 100);
tt.addMachineType("Crop Farm")
.addInfo("Controller block for the Extreme Industrial Greenhouse")
.addInfo(Author)
- .addInfo("Grow your crops like a chad !")
+ .addInfo("Grow your crops like a chad!")
.addInfo("Use screwdriver to enable/change/disable setup mode")
.addInfo("Use screwdriver while sneaking to enable/disable IC2 mode")
- .addInfo("Use wire cutters to give incoming IC2 crops 0 humidity")
- .addInfo("Uses 1000L of water per crop per operation")
+ .addInfo("Use wire cutters to give incoming IC2 seeds 0 humidity")
+ .addInfo("Uses " + EIG_BALANCE_WATER_USAGE_PER_SEED + "L of water per seed per operation")
.addInfo(
- "If there are >= " + EIG_CROP_LIMIT_FOR_WEEDEX9000_REQUIREMENT
- + " crops -> Uses 1L of Weed-EX 9000 per crop per second")
- .addInfo("Otherwise, around 1% of crops will die each operation")
- .addInfo("You can insert fertilizer each operation to get more drops (max +400%)")
- .addInfo("-------------------- SETUP MODE --------------------")
+ "Uses 1L of " + new FluidStack(WEEDEX_FLUID, 1).getLocalizedName()
+ + " per operation per seed if it contains more than "
+ + EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT
+ + " seeds")
+ .addInfo("Otherwise, around 1% of seeds will be voided each operation")
+ .addInfo("You can insert fertilizer each operation to get more drops (max + " + fertilizerBoostMax + ")")
+ .addInfo("--------------------- SETUP MODE ---------------------")
.addInfo("Does not take power")
.addInfo("There are two modes: input / output")
.addInfo("Input mode: machine will take seeds from input bus and plant them")
.addInfo("[IC2] You need to also input block that is required under the crop")
- .addInfo("Output mode: machine will take planted seeds and output them")
- .addInfo("-------------------- NORMAL CROPS --------------------")
- .addInfo("Minimal tier: " + voltageTooltipFormatted(4))
- .addInfo("Starting with 1 slot")
- .addInfo("Every slot gives 64 crops")
- .addInfo("Every tier past " + voltageTooltipFormatted(4) + ", slots are multiplied by 2")
- .addInfo("Base process time: 5 sec")
- .addInfo(
- "Process time is divided by number of tiers past " + voltageTooltipFormatted(3) + " (Minimum 1 sec)")
- .addInfo("All crops are grown at the end of the operation")
- .addInfo("Will automatically craft seeds if they are not dropped")
- .addInfo("1 Fertilizer per 1 crop +200%")
- .addInfo("-------------------- IC2 CROPS --------------------")
- .addInfo("Minimal tier: " + voltageTooltipFormatted(EIG_BALANCE_IC2_ACCELERATOR_TIER + 1))
- .addInfo("Need " + voltageTooltipFormatted(EIG_BALANCE_IC2_ACCELERATOR_TIER + 1) + " glass tier")
- .addInfo("Starting with 4 slots")
- .addInfo("Every slot gives 1 crop")
- .addInfo(
- "Every tier past " + voltageTooltipFormatted(EIG_BALANCE_IC2_ACCELERATOR_TIER + 1)
- + ", slots are multiplied by 4")
- .addInfo("Process time: 5 sec")
- .addInfo("All crops are accelerated by x" + (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER) + " times")
- .addInfo("1 Fertilizer per 1 crop +10%")
- .addInfo(StructureHologram)
+ .addInfo("Output mode: machine will take planted seeds and output them");
+ EIGModes.addTooltipInfo(tt);
+ tt.addInfo(StructureHologram)
.addSeparator()
.beginStructureBlock(5, 6, 5, false)
.addController("Front bottom center")
@@ -377,33 +353,262 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
return info.toArray(new String[] {});
}
+ // endregion tooltip
+
+ // region (de)constructor
+
+ public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(int aID, String aName, String aNameRegional) {
+ super(aID, aName, aNameRegional);
+ }
+
+ public GT_MetaTileEntity_ExtremeIndustrialGreenhouse(String aName) {
+ super(aName);
+ }
+
+ @Override
+ public IMetaTileEntity newMetaEntity(IGregTechTileEntity iGregTechTileEntity) {
+ return new GT_MetaTileEntity_ExtremeIndustrialGreenhouse(this.mName);
+ }
+
+ @Override
+ public void onFirstTick(IGregTechTileEntity aBaseMetaTileEntity) {
+ super.onFirstTick(aBaseMetaTileEntity);
+ if (this.toMigrate != null) {
+ // Create the new buckets respectively.
+ if (this.mode == EIGModes.IC2) {
+ for (EIGMigrationHolder holder : toMigrate) {
+ // We will have to revalidate the seeds on the next cycle.
+ this.buckets
+ .add(new EIGIC2Bucket(holder.seed, holder.count, holder.supportBlock, holder.useNoHumidity));
+ }
+ } else {
+ this.mode = EIGModes.Normal;
+ for (EIGMigrationHolder holder : toMigrate) {
+ holder.seed.stackSize = holder.count;
+ EIGBucket bucket = this.mode.tryCreateNewBucket(this, holder.seed, Integer.MAX_VALUE, false);
+ if (bucket == null) {
+ // if we somehow can't grow the seed, try ejecting it at least.
+ holder.seed.stackSize = holder.count;
+ this.addOutput(holder.seed);
+ continue;
+ }
+ this.buckets.add(bucket);
+ }
+ }
+ }
+ }
+
+ /**
+ * Ejects all the seeds when the controller is broken.
+ */
+ @Override
+ public void onRemoval() {
+ super.onRemoval();
+
+ // attempt to empty all buckets
+ buckets.removeIf(this::tryEmptyBucket);
+ if (buckets.isEmpty()) return;
+
+ // attempt to drop non outputted items into the world.
+ IGregTechTileEntity mte = this.getBaseMetaTileEntity();
+ for (EIGBucket bucket : this.buckets) {
+ for (ItemStack stack : bucket.tryRemoveSeed(bucket.getSeedCount(), false)) {
+ EntityItem entityitem = new EntityItem(
+ mte.getWorld(),
+ mte.getXCoord(),
+ mte.getYCoord(),
+ mte.getZCoord(),
+ stack);
+ entityitem.delayBeforeCanPickup = 10;
+ mte.getWorld()
+ .spawnEntityInWorld(entityitem);
+ }
+ }
+ }
+
+ // endregion
+
+ // region tool interactions
+
+ /**
+ * Right click = change setup phase
+ * Shift+Right Click = change EIG Mode
+ */
+ @Override
+ public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ,
+ ItemStack aTool) {
+ if (aPlayer.isSneaking()) {
+ tryChangeMode(aPlayer);
+ } else {
+ tryChangeSetupPhase(aPlayer);
+ }
+ }
+
+ /**
+ * Right-Clicking with wire cutters toggle no hydration mode.
+ */
+ @Override
+ public boolean onWireCutterRightClick(ForgeDirection side, ForgeDirection wrenchingSide, EntityPlayer aPlayer,
+ float aX, float aY, float aZ, ItemStack aTool) {
+ this.tryChangeHumidityMode(aPlayer);
+ return true;
+ }
+
+ // endregion tool interactions
+
+ // region mode change standardisation
+
+ /**
+ * Attempts to change the setup phase of the EIG to the next mode
+ *
+ * @param aPlayer The player to notify for success and errors
+ */
+ private void tryChangeSetupPhase(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the setup phase change messages.
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't enable/disable setup if the machine is working!");
+ return;
+ }
+ this.setupPhase++;
+ if (this.setupPhase == 3) this.setupPhase = 0;
+ String phaseChangeMessage = "EIG is now running in ";
+ switch (this.setupPhase) {
+ case 0:
+ phaseChangeMessage += "operational mode.";
+ break;
+ case 1:
+ phaseChangeMessage += "seed input mode.";
+ break;
+ case 2:
+ phaseChangeMessage += "seed output mode.";
+ break;
+ default:
+ phaseChangeMessage += "an invalid mode please send us a ticket!";
+ break;
+ }
+ this.updateSeedLimits();
+ GT_Utility.sendChatToPlayer(aPlayer, phaseChangeMessage);
+ }
+
+ /**
+ * Attempts to change the mode of the EIG to the next mode.
+ *
+ * @param aPlayer The player to notify of success and errors
+ */
+ private void tryChangeMode(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the mode change messages.
+ if (this.mMaxProgresstime > 0) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't change mode if the machine is working!");
+ return;
+ }
+ if (!this.buckets.isEmpty()) {
+ GT_Utility.sendChatToPlayer(aPlayer, "You can't change mode if there are seeds inside!");
+ return;
+ }
+ this.mode = EIGModes.getNextMode(this.mode);
+ this.updateSeedLimits();
+ GT_Utility.sendChatToPlayer(aPlayer, "Changed mode to: " + this.mode.getName());
+ }
+
+ /**
+ * Attempts to toggle the hydration mode of the EIG.
+ *
+ * @param aPlayer The player to notify for success and errors
+ */
+ private void tryChangeHumidityMode(EntityPlayer aPlayer) {
+ // TODO: Create l10n entries for the humidity status interactions.
+ this.useNoHumidity = !this.useNoHumidity;
+ if (this.useNoHumidity) {
+ GT_Utility.sendChatToPlayer(aPlayer, "No Humidity mode enabled.");
+ } else {
+ GT_Utility.sendChatToPlayer(aPlayer, "No Humidity mode disabled.");
+ }
+ }
+
+ // endregion mode change standardisation
+
+ // region (de)serialisations
+
@Override
public void saveNBTData(NBTTagCompound aNBT) {
super.saveNBTData(aNBT);
- aNBT.setInteger("EIG_MATH_VERSION", EIG_MATH_VERSION);
- aNBT.setByte("glasTier", glasTier);
- aNBT.setInteger("setupphase", setupphase);
- aNBT.setBoolean("isIC2Mode", isIC2Mode);
- aNBT.setBoolean("isNoHumidity", isNoHumidity);
- aNBT.setInteger("mStorageSize", mStorage.size());
- for (int i = 0; i < mStorage.size(); i++) aNBT.setTag(
- "mStorage." + i,
- mStorage.get(i)
- .toNBTTagCompound());
+ aNBT.setInteger("version", NBT_REVISION);
+ aNBT.setByte("glassTier", this.glassTier);
+ aNBT.setInteger("setupPhase", this.setupPhase);
+ aNBT.setString("mode", this.mode.getName());
+ aNBT.setBoolean("isNoHumidity", this.useNoHumidity);
+ NBTTagList bucketListNBT = new NBTTagList();
+ for (EIGBucket b : this.buckets) {
+ bucketListNBT.appendTag(b.save());
+ }
+ aNBT.setTag(
+ "progress",
+ this.dropTracker.intersect(this.guiDropTracker)
+ .save());
+ aNBT.setTag("buckets", bucketListNBT);
+ }
+
+ private static class EIGMigrationHolder {
+
+ public final ItemStack seed;
+ public final ItemStack supportBlock;
+ public final boolean useNoHumidity;
+ public int count;
+ public boolean isValid = false;
+
+ public EIGMigrationHolder(NBTTagCompound nbt) {
+ this.seed = readItemStackFromNBT(nbt.getCompoundTag("input"));
+ this.count = this.seed.stackSize;
+ this.seed.stackSize = 1;
+ this.supportBlock = nbt.hasKey("undercrop", 10) ? readItemStackFromNBT(nbt.getCompoundTag("undercrop"))
+ : null;
+ this.useNoHumidity = nbt.getBoolean("noHumidity");
+ this.isValid = true;
+ }
+
+ public String getKey() {
+ if (this.supportBlock == null) return seed.toString();
+ return "(" + this.seed.toString() + "," + this.supportBlock + ")";
+ }
+
}
@Override
public void loadNBTData(NBTTagCompound aNBT) {
super.loadNBTData(aNBT);
- oldVersion = aNBT.hasKey("EIG_MATH_VERSION") ? aNBT.getInteger("EIG_MATH_VERSION") : -1;
- glasTier = aNBT.getByte("glasTier");
- setupphase = aNBT.getInteger("setupphase");
- isIC2Mode = aNBT.getBoolean("isIC2Mode");
- isNoHumidity = aNBT.getBoolean("isNoHumidity");
- for (int i = 0; i < aNBT.getInteger("mStorageSize"); i++)
- mStorage.add(new GreenHouseSlot(aNBT.getCompoundTag("mStorage." + i)));
+ int revision = aNBT.hasKey("version", 3) ? aNBT.getInteger("version") : 0;
+ if (revision <= 0) {
+ // migrate old EIG with greenhouse slots to new Bucker mode and fix variable names
+ this.glassTier = aNBT.getByte("glasTier");
+ this.setupPhase = aNBT.getInteger("setupphase");
+ this.mode = aNBT.getBoolean("isIC2Mode") ? EIGModes.IC2 : EIGModes.Normal;
+ this.useNoHumidity = aNBT.getBoolean("isNoHumidity");
+ // aggregate all seed types
+ HashMap<String, EIGMigrationHolder> toMigrate = new HashMap<>();
+ for (int i = 0; i < aNBT.getInteger("mStorageSize"); i++) {
+ EIGMigrationHolder holder = new EIGMigrationHolder(aNBT.getCompoundTag("mStorage." + i));
+ if (toMigrate.containsKey(holder.getKey())) {
+ toMigrate.get(holder.getKey()).count += holder.count;
+ } else {
+ toMigrate.put(holder.getKey(), holder);
+ }
+ }
+
+ this.toMigrate = toMigrate.values();
+ } else {
+ this.glassTier = aNBT.getByte("glassTier");
+ this.setupPhase = aNBT.getInteger("setupPhase");
+ this.mode = EIGModes.getModeFromName(aNBT.getString("mode"));
+ this.useNoHumidity = aNBT.getBoolean("isNoHumidity");
+ this.mode.restoreBuckets(aNBT.getTagList("buckets", 10), this.buckets);
+ new EIGDropTable(aNBT.getTagList("progress", 10)).addTo(this.dropTracker);
+ }
}
+ // endregion
+
+ // region crop visuals rendering
+
@SideOnly(Side.CLIENT)
public void spawnVisualCrops(World world, int x, int y, int z, int age) {
CropRenderer crop = new CropRenderer(world, x, y, z, age);
@@ -427,46 +632,129 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
}
}
- @Override
- public void construct(ItemStack itemStack, boolean b) {
- buildPiece(STRUCTURE_PIECE_MAIN, itemStack, b, 2, 5, 0);
+ // endregion crop visuals rendering
+
+ /**
+ * Calculates the total amount of seeds in the EIG
+ *
+ * @return The number of seeds in the EIG.
+ */
+ private int getTotalSeedCount() {
+ // null check is to prevent a occasional weird NPE from MUI
+ return this.buckets.parallelStream()
+ .reduce(0, (b, t) -> b + t.getSeedCount(), Integer::sum);
}
- private void updateMaxSlots() {
- int tier = getVoltageTier();
- if (tier < (isIC2Mode ? (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1) : 4)) mMaxSlots = 0;
- else if (isIC2Mode) mMaxSlots = 4 << (2 * (tier - (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1)));
- else mMaxSlots = 1 << (tier - 4);
+ /**
+ * Updates the max seed counts of the machine
+ */
+ private void updateSeedLimits() {
+ this.maxSeedTypes = this.mode.getSlotCount(getVoltageTier());
+ this.maxSeedCount = this.maxSeedTypes * this.mode.getSeedCapacityPerSlot();
+ }
+
+ /**
+ * Attempts to drain the multi of a given fluid, will only return true if all fluid is consumed.
+ *
+ * @param toConsume A fluid stack of the fluid to consume.
+ * @param drainPartial True to allow partial consumption.
+ * @return True when all the fluid has been consumed.
+ */
+ private boolean tryDrain(FluidStack toConsume, boolean drainPartial) {
+ // Nothing to consume = success I guess?
+ if (toConsume == null || toConsume.amount <= 0) return true;
+ // TODO: improve fluid draining logic.
+ List<FluidStack> fluids = this.getStoredFluids();
+ List<FluidStack> fluidsToUse = new ArrayList<>(fluids.size());
+ int remaining = toConsume.amount;
+ for (FluidStack fluid : fluids) {
+ if (fluid.isFluidEqual(toConsume)) {
+ remaining -= fluid.amount;
+ fluidsToUse.add(fluid);
+ if (remaining <= 0) break;
+ }
+ }
+ if (!drainPartial && remaining > 0 && !debug) return false;
+ boolean success = remaining <= 0;
+ remaining = toConsume.amount - Math.max(0, remaining);
+ for (FluidStack fluid : fluidsToUse) {
+ int used = Math.min(remaining, fluid.amount);
+ fluid.amount -= used;
+ remaining -= used;
+ }
+ return success;
+ }
+
+ /**
+ * Checks if a stack contains an item that can be used as fertilizer
+ *
+ * @param item A stack of item to validate
+ * @return True if the item can be consumed as fertilizer.
+ */
+ public static boolean isFertilizer(ItemStack item) {
+ if (item == null || item.stackSize <= 0) return false;
+ for (ItemStack fert : FERTILIZER_ITEM_LIST) {
+ if (GT_Utility.areStacksEqual(item, fert)) return true;
+ }
+ return false;
+ }
+
+ private boolean tryEmptyBucket(EIGBucket bucket) {
+ // check if it's already empty
+ if (bucket.getSeedCount() <= 0) return true;
+
+ // check if we have an ME output bus to output to.
+ for (GT_MetaTileEntity_Hatch_OutputBus tHatch : filterValidMTEs(mOutputBusses)) {
+ if (!(tHatch instanceof GT_MetaTileEntity_Hatch_OutputBus_ME)) continue;
+ for (ItemStack stack : bucket.tryRemoveSeed(bucket.getSeedCount(), false)) {
+ ((GT_MetaTileEntity_Hatch_OutputBus_ME) tHatch).store(stack);
+ }
+ return true;
+ }
+
+ // Else attempt to empty the bucket while not voiding anything.
+ ItemStack[] simulated = bucket.tryRemoveSeed(1, true);
+ VoidProtectionHelper helper = new VoidProtectionHelper().setMachine(this, true, false)
+ .setItemOutputs(simulated)
+ .setMaxParallel(bucket.getSeedCount())
+ .build();
+ if (helper.getMaxParallel() > 0) {
+ for (ItemStack toOutput : bucket.tryRemoveSeed(helper.getMaxParallel(), false)) {
+ for (GT_MetaTileEntity_Hatch_OutputBus tHatch : filterValidMTEs(mOutputBusses)) {
+ if (tHatch.storeAll(toOutput)) break;
+ }
+ }
+ }
+ return bucket.getSeedCount() <= 0;
}
@Override
@NotNull
public CheckRecipeResult checkProcessing() {
int tier = getVoltageTier();
- updateMaxSlots();
-
- if (oldVersion != EIG_MATH_VERSION) {
- for (GreenHouseSlot slot : mStorage) slot.recalculate(this, getBaseMetaTileEntity().getWorld());
- oldVersion = EIG_MATH_VERSION;
- }
+ updateSeedLimits();
- if (setupphase > 0) {
- if ((mStorage.size() >= mMaxSlots && setupphase == 1) || (mStorage.isEmpty() && setupphase == 2))
+ if (setupPhase > 0) {
+ if ((buckets.size() >= maxSeedTypes && setupPhase == 1) || (buckets.isEmpty() && setupPhase == 2))
return CheckRecipeResultRegistry.NO_RECIPE;
- if (setupphase == 1) {
+ if (setupPhase == 1) {
List<ItemStack> inputs = getStoredInputs();
for (ItemStack input : inputs) {
addCrop(input);
- if (mStorage.size() >= mMaxSlots) break;
+ if (buckets.size() >= maxSeedTypes) break;
+ }
+ } else if (setupPhase == 2) {
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ if (tryEmptyBucket(bucket)) {
+ iterator.remove();
+ } else {
+ this.mMaxProgresstime = 20;
+ this.lEUt = 0;
+ return CheckRecipeResultRegistry.ITEM_OUTPUT_FULL;
+ }
}
- } else if (setupphase == 2) {
- tryOutputAll(mStorage, s -> {
- ArrayList<ItemStack> l = new ArrayList<>(2);
- l.add(((GreenHouseSlot) s).input.copy());
- if (((GreenHouseSlot) s).undercrop != null) l.add(((GreenHouseSlot) s).undercrop.copy());
- return l;
- });
}
this.updateSlots();
@@ -476,49 +764,56 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
this.mEfficiencyIncrease = 10000;
return CheckRecipeResultRegistry.SUCCESSFUL;
}
- if (mStorage.size() > mMaxSlots) return SimpleCheckRecipeResult.ofFailure("EIG_slotoverflow");
- if (mStorage.isEmpty()) return CheckRecipeResultRegistry.NO_RECIPE;
-
- waterusage = 0;
- weedexusage = 0;
- for (GreenHouseSlot s : mStorage) waterusage += s.input.stackSize;
- if (waterusage >= EIG_CROP_LIMIT_FOR_WEEDEX9000_REQUIREMENT) weedexusage = waterusage;
- waterusage *= 1000;
-
- List<GT_MetaTileEntity_Hatch_Input> fluids = mInputHatches;
- List<GT_MetaTileEntity_Hatch_Input> fluidsToUse = new ArrayList<>(fluids.size());
- int watercheck = waterusage;
- FluidStack waterStack = new FluidStack(FluidRegistry.WATER, 1);
- for (GT_MetaTileEntity_Hatch_Input i : fluids) {
- if (!i.isValid()) continue;
- if (i instanceof GT_MetaTileEntity_Hatch_MultiInput) {
- int amount = ((GT_MetaTileEntity_Hatch_MultiInput) i).getFluidAmount(waterStack);
- if (amount == 0) continue;
- watercheck -= amount;
- } else {
- FluidStack stack = i.getDrainableStack();
- if (stack == null) continue;
- if (!stack.isFluidEqual(waterStack)) continue;
- if (stack.amount <= 0) continue;
- watercheck -= stack.amount;
+ if (this.maxSeedTypes < this.buckets.size()) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_slotoverflow");
+ }
+ int seedCount = this.getTotalSeedCount();
+ if (this.maxSeedCount < seedCount) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_seedOverflow");
+ }
+
+ // Kick out bad buckets.
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ if (bucket.isValid() || bucket.revalidate(this)) continue;
+ // attempt to empty the bucket
+ tryEmptyBucket(bucket);
+ // remove empty bucket and attempt to revalidate invalid buckets
+ if (bucket.getSeedCount() <= 0) {
+ iterator.remove();
}
- fluidsToUse.add(i);
- if (watercheck <= 0) break;
}
- if (watercheck > 0 && !debug) return SimpleCheckRecipeResult.ofFailure("EIG_missingwater");
- watercheck = waterusage;
- for (GT_MetaTileEntity_Hatch_Input i : fluidsToUse) {
- int used = i.drain(watercheck, true).amount;
- watercheck -= used;
+
+ if (this.buckets.isEmpty()) return CheckRecipeResultRegistry.NO_RECIPE;
+
+ // Compute the Weed-EX and water requirements,
+ // TODO: We only really need to update water usage and WeedEX usage when adding seeds or when loading NBT.
+ this.waterUsage = seedCount * 1000;
+ this.weedEXUsage = (seedCount >= EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT ? seedCount : 0)
+ * this.mode.getWeedEXMultiplier();
+
+ // Consume water, fail if we don't have enough
+ if (!this.tryDrain(new FluidStack(FluidRegistry.WATER, this.waterUsage), false)) {
+ return SimpleCheckRecipeResult.ofFailure("EIG_missingwater");
}
- // weedex
- if (weedexusage > 0 && !this.depleteInput(new FluidStack(weedex, isIC2Mode ? weedexusage * 5 : weedexusage))) {
+ // Consume weed ex, if there isn't enough we consume what's there but don't fail
+ if (weedEXUsage > 0 && !this.tryDrain(new FluidStack(WEEDEX_FLUID, this.weedEXUsage), true)) {
IGregTechTileEntity baseMTE = this.getBaseMetaTileEntity();
- int toKill = baseMTE.getRandomNumber((int) ((double) weedexusage * 0.02d) + 1);
- while (toKill > 0) {
- GreenHouseSlot removed = mStorage.remove(baseMTE.getRandomNumber(mStorage.size()));
- toKill -= removed.input.stackSize;
+ // Cap seed murder to the Weed EX limit, no more senseless murder of bystanders
+ int killLimit = (seedCount - EIG_BALANCE_WEED_EX_USAGE_BEGINS_AT + 1);
+ int toKill = Math.min(killLimit, baseMTE.getRandomNumber((int) ((double) seedCount * 0.02d) + 1));
+ if (toKill > 0) {
+ for (Iterator<EIGBucket> iterator = this.buckets.iterator(); iterator.hasNext();) {
+ EIGBucket bucket = iterator.next();
+ ItemStack[] removed = bucket.tryRemoveSeed(toKill, false);
+ if (removed == null || removed[0].stackSize <= 0) continue;
+ toKill -= removed[0].stackSize;
+ // if bucket is empty, yeet it out.
+ if (bucket.getSeedCount() <= 0) iterator.remove();
+ // if we are out of crops to kill we can just leave
+ if (toKill <= 0) break;
+ }
}
}
@@ -527,74 +822,96 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
// IC2 +10% per fertilizer per crop per operation
// NORMAL +200% per fertilizer per crop per operation
- int boost = 0;
- int maxboost = 0;
- for (GreenHouseSlot s : mStorage) maxboost += s.input.stackSize * (isIC2Mode ? 40 : 2);
+ int consumedFertilizer = 0;
+ int maxFertilizerToConsume = 0;
+ for (EIGBucket bucket : this.buckets)
+ maxFertilizerToConsume += bucket.getSeedCount() * this.mode.getMaxFertilizerUsagePerSeed();
ArrayList<ItemStack> inputs = getStoredInputs();
for (ItemStack i : inputs) {
- if ((i.getItem() == Items.dye && i.getItemDamage() == 15)
- || (forestryfertilizer != null && (i.getItem() == forestryfertilizer))
- || (GT_Utility.areStacksEqual(i, Ic2Items.fertilizer))) {
- int used = Math.min(i.stackSize, maxboost - boost);
+ if (isFertilizer(i)) {
+ int used = Math.min(i.stackSize, maxFertilizerToConsume - consumedFertilizer);
i.stackSize -= used;
- boost += used;
+ consumedFertilizer += used;
}
- if (boost == maxboost) break;
+ if (consumedFertilizer == maxFertilizerToConsume) break;
}
+ double multiplier = 1.d
+ + (((double) consumedFertilizer / (double) maxFertilizerToConsume) * EIG_BALANCE_MAX_FERTILIZER_BOOST);
- double multiplier = 1.d + (((double) boost / (double) maxboost) * 4d);
-
- if (isIC2Mode) {
- if (glasTier < (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1))
+ // compute drops based on the drop tracker
+ this.guiDropTracker = new EIGDropTable();
+ if (this.mode == EIGModes.IC2) {
+ if (glassTier < (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1))
return SimpleCheckRecipeResult.ofFailure("EIG_ic2glass");
this.mMaxProgresstime = 100;
- List<ItemStack> outputs = new ArrayList<>();
- for (int i = 0; i < Math.min(mMaxSlots, mStorage.size()); i++) outputs.addAll(
- mStorage.get(i)
- .getIC2Drops(
- this,
- ((double) this.mMaxProgresstime * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER)) * multiplier));
- this.mOutputItems = outputs.toArray(new ItemStack[0]);
- } else {
+ // determine the amount of time we are simulating on the seed.
+ double timeElapsed = ((double) this.mMaxProgresstime * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER));
+ // Add drops to the drop tracker for each seed bucket.
+ for (EIGBucket bucket : this.buckets) {
+ bucket.addProgress(timeElapsed * multiplier, this.guiDropTracker);
+ }
+ } else if (this.mode == EIGModes.Normal) {
this.mMaxProgresstime = Math.max(20, 100 / (tier - 3)); // Min 1 s
- List<ItemStack> outputs = new ArrayList<>();
- for (int i = 0; i < Math.min(mMaxSlots, mStorage.size()); i++) {
- for (ItemStack drop : mStorage.get(i)
- .getDrops()) {
- ItemStack s = drop.copy();
- s.stackSize = (int) ((double) s.stackSize * multiplier);
- outputs.add(s);
- }
+ for (EIGBucket bucket : this.buckets) {
+ bucket.addProgress(multiplier, this.guiDropTracker);
}
- this.mOutputItems = outputs.toArray(new ItemStack[0]);
}
- this.lEUt = -(int) ((double) GT_Values.V[tier] * 0.99d);
+
+ this.guiDropTracker.addTo(this.dropTracker, multiplier);
+ this.mOutputItems = this.dropTracker.getDrops();
+
+ // consume power
+ this.lEUt = -(long) ((double) GT_Values.V[tier] * 0.99d);
this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000);
this.mEfficiencyIncrease = 10000;
this.updateSlots();
return CheckRecipeResultRegistry.SUCCESSFUL;
}
- @Override
- public boolean checkMachine(IGregTechTileEntity iGregTechTileEntity, ItemStack itemStack) {
- mCasing = 0;
- glasTier = 0;
- if (debug) glasTier = 8;
-
- if (!checkPiece(STRUCTURE_PIECE_MAIN, 2, 5, 0)) return false;
-
- if (this.glasTier < 8 && !this.mEnergyHatches.isEmpty())
- for (GT_MetaTileEntity_Hatch_Energy hatchEnergy : this.mEnergyHatches)
- if (this.glasTier < hatchEnergy.mTier) return false;
+ private ItemStack addCrop(ItemStack input) {
+ return addCrop(input, false) ? input : null;
+ }
- boolean valid = this.mMaintenanceHatches.size() == 1 && !this.mEnergyHatches.isEmpty() && this.mCasing >= 70;
+ /**
+ * Adds a seed to the EIG
+ *
+ * @param input The item to add to the EIG.
+ * @param simulate Set to true to not actually consume any input.
+ * @return True if all items were consumed
+ */
+ private boolean addCrop(ItemStack input, boolean simulate) {
+ // Nothing to add = success since technically nothing should have changed?
+ if (input == null || input.stackSize <= 0) return true;
+
+ // For safety's sake copy the input if we are simulating to make sure we aren't modifying it
+ if (simulate) input = input.copy();
+
+ // Cap input count to current seed max
+ int addCap = Math.min(input.stackSize, this.maxSeedCount - this.getTotalSeedCount());
+ if (addCap <= 0) return false;
+
+ // Attempt to find a compatible bucket that already exists
+ for (EIGBucket bucket : this.buckets) {
+ int consumed = bucket.tryAddSeed(this, input, addCap, simulate);
+ if (consumed <= 0) continue;
+ return input.stackSize <= 0;
+ }
- if (valid) updateMaxSlots();
+ // Check if we have space for a new bucket
+ if (this.maxSeedTypes <= this.buckets.size()) {
+ return false;
+ }
- return valid;
+ // try creating a new bucket, this only returns valid buckets.
+ EIGBucket bucket = this.mode.tryCreateNewBucket(this, input, addCap, simulate);
+ if (bucket == null) return false;
+ this.buckets.add(bucket);
+ return input.stackSize <= 0;
}
+ // region ui
+
private static final UIInfo<?, ?> GreenhouseUI = createKTMetaTileEntityUI(
KT_ModulaUIContainer_ExtremeIndustrialGreenhouse::new);
@@ -630,18 +947,17 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
if (aStack == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
GT_MetaTileEntity_ExtremeIndustrialGreenhouse mte = parent.get();
if (mte == null) return super.transferStackInSlot(aPlayer, aSlotIndex);
- if (mte.mStorage.size() >= mte.mMaxSlots) return super.transferStackInSlot(aPlayer, aSlotIndex);
+ // if (mte.buckets.size() >= mte.maxSeedTypes) return super.transferStackInSlot(aPlayer, aSlotIndex);
if (mte.mMaxProgresstime > 0) {
GT_Utility.sendChatToPlayer(aPlayer, EnumChatFormatting.RED + "Can't insert while running !");
return super.transferStackInSlot(aPlayer, aSlotIndex);
}
- if (mte.addCrop(aStack) != null) {
- if (aStack.stackSize == 0) s.putStack(null);
- else s.putStack(aStack);
- detectAndSendChanges();
- return null;
- }
- return super.transferStackInSlot(aPlayer, aSlotIndex);
+
+ mte.addCrop(aStack);
+ if (aStack.stackSize <= 0) s.putStack(null);
+ else s.putStack(aStack);
+ detectAndSendChanges();
+ return null;
}
}
@@ -660,38 +976,32 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
.setSize(16, 16));
}
- DynamicInventory<GreenHouseSlot> dynamicInventory = new DynamicInventory<>(
+ EIGDynamicInventory<EIGBucket> dynamicInventory = new EIGDynamicInventory<>(
128,
60,
- () -> mMaxSlots,
- mStorage,
- s -> s.input).allowInventoryInjection(this::addCrop)
- .allowInventoryExtraction(mStorage::remove)
- .allowInventoryReplace((i, stack) -> {
- if (!isIC2Mode) {
- GreenHouseSlot slot = mStorage.get(i);
- if (GT_Utility.areStacksEqual(stack, slot.input)) {
- if (slot.input.stackSize < 64) {
- slot.addAll(
- this.getBaseMetaTileEntity()
- .getWorld(),
- stack);
- return stack;
- }
- return null;
- }
- if (!addCrop(stack, i, true)) return null;
- slot = mStorage.remove(i);
- addCrop(stack, i, false);
- return slot.input;
- } else {
- if (stack.stackSize != 1) return null;
- if (!addCrop(stack, i, true)) return null;
- GreenHouseSlot slot = mStorage.remove(i);
- addCrop(stack, i, false);
- return slot.input;
+ () -> this.maxSeedTypes,
+ () -> this.maxSeedCount,
+ this.buckets::size,
+ this::getTotalSeedCount,
+ this.buckets,
+ EIGBucket::getSeedStack).allowInventoryInjection(this::addCrop)
+ .allowInventoryExtraction((bucket, player) -> {
+ if (bucket == null) return null;
+ int maxRemove = bucket.getSeedStack()
+ .getMaxStackSize();
+ ItemStack[] outputs = bucket.tryRemoveSeed(maxRemove, false);
+ if (outputs == null || outputs.length <= 0) return null;
+ ItemStack ret = outputs[0];
+ for (int i = 1; i < outputs.length; i++) {
+ ItemStack suppertItem = outputs[i];
+ if (!player.inventory.addItemStackToInventory(suppertItem)) {
+ player.entityDropItem(suppertItem, 0.f);
+ } ;
}
+ if (bucket.getSeedCount() <= 0) this.buckets.remove(bucket);
+ return ret;
})
+ // TODO: re-add allow inventory replace?
.setEnabled(() -> this.mMaxProgresstime == 0);
@Override
@@ -712,8 +1022,8 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
builder.widget(
dynamicInventory.asWidget(builder, buildContext)
.setPos(10, 16)
- .setBackground(new Rectangle().setColor(Color.rgb(163, 163, 198)))
.setEnabled(w -> isInInventory));
+
builder.widget(
new CycleButtonWidget().setToggle(() -> isInInventory, i -> isInInventory = i)
.setTextureGetter(i -> i == 0 ? new Text("Inventory") : new Text("Status"))
@@ -754,20 +1064,10 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
.widget(
new Column().widget(
new CycleButtonWidget().setLength(3)
- .setGetter(() -> setupphase)
+ .setGetter(() -> this.setupPhase)
.setSetter(val -> {
if (!(player instanceof EntityPlayerMP)) return;
- if (this.mMaxProgresstime > 0) {
- GT_Utility.sendChatToPlayer(
- player,
- "You can't enable/disable setup if the machine is working!");
- return;
- }
- this.setupphase = val;
- GT_Utility.sendChatToPlayer(
- player,
- "EIG is now running in " + (this.setupphase == 1 ? "setup mode (input)."
- : (this.setupphase == 2 ? "setup mode (output)." : "normal operation.")));
+ tryChangeSetupPhase(player);
})
.addTooltip(0, new Text("Operating").color(Color.GREEN.dark(3)))
.addTooltip(1, new Text("Input").color(Color.YELLOW.dark(3)))
@@ -786,25 +1086,10 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
.addTooltip("Setup mode"))
.widget(
new CycleButtonWidget().setLength(2)
- .setGetter(() -> isIC2Mode ? 1 : 0)
+ .setGetter(() -> this.mode.getUIIndex())
.setSetter(val -> {
if (!(player instanceof EntityPlayerMP)) return;
- if (this.mMaxProgresstime > 0) {
- GT_Utility.sendChatToPlayer(
- player,
- "You can't change IC2 mode if the machine is working!");
- return;
- }
- if (!mStorage.isEmpty()) {
- GT_Utility.sendChatToPlayer(
- player,
- "You can't change IC2 mode if there are seeds inside!");
- return;
- }
- this.isIC2Mode = val == 1;
- GT_Utility.sendChatToPlayer(
- player,
- "IC2 mode is now " + (this.isIC2Mode ? "enabled" : "disabled."));
+ tryChangeMode(player);
})
.addTooltip(0, new Text("Disabled").color(Color.RED.dark(3)))
.addTooltip(1, new Text("Enabled").color(Color.GREEN.dark(3)))
@@ -820,11 +1105,10 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
.addTooltip("IC2 mode"))
.widget(
new CycleButtonWidget().setLength(2)
- .setGetter(() -> isNoHumidity ? 1 : 0)
+ .setGetter(() -> useNoHumidity ? 1 : 0)
.setSetter(val -> {
if (!(player instanceof EntityPlayerMP)) return;
- isNoHumidity = val == 1;
- GT_Utility.sendChatToPlayer(player, "Give incoming crops no humidity " + isNoHumidity);
+ this.tryChangeHumidityMode(player);
})
.addTooltip(0, new Text("Disabled").color(Color.RED.dark(3)))
.addTooltip(1, new Text("Enabled").color(Color.GREEN.dark(3)))
@@ -855,21 +1139,25 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
return builder.build();
}
- private HashMap<ItemStack, Double> GUIDropProgress = new HashMap<>();
-
@Override
protected String generateCurrentRecipeInfoString() {
- if (!isIC2Mode) return super.generateCurrentRecipeInfoString();
StringBuilder ret = new StringBuilder(EnumChatFormatting.WHITE + "Progress: ")
- .append(String.format("%,.2f", (double) mProgresstime / 20))
+ .append(String.format("%,.2f", (double) this.mProgresstime / 20))
.append("s / ")
- .append(String.format("%,.2f", (double) mMaxProgresstime / 20))
+ .append(String.format("%,.2f", (double) this.mMaxProgresstime / 20))
.append("s (")
- .append(String.format("%,.1f", (double) mProgresstime / mMaxProgresstime * 100))
+ .append(String.format("%,.1f", (double) this.mProgresstime / this.mMaxProgresstime * 100))
.append("%)\n");
- for (Map.Entry<ItemStack, Double> drop : GUIDropProgress.entrySet()) {
- int outputSize = Arrays.stream(mOutputItems)
+ for (Map.Entry<ItemStack, Double> drop : this.synchedGUIDropTracker.entrySet()
+ .stream()
+ .sorted(
+ Comparator.comparing(
+ a -> a.getKey()
+ .toString()
+ .toLowerCase()))
+ .collect(Collectors.toList())) {
+ int outputSize = Arrays.stream(this.mOutputItems)
.filter(s -> s.isItemEqual(drop.getKey()))
.mapToInt(i -> i.stackSize)
.sum();
@@ -892,26 +1180,24 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
(double) outputSize / (mMaxProgresstime / 20)));
}
}
-
return ret.toString();
}
@Override
protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
- screenElements.widget(new FakeSyncWidget.BooleanSyncer(() -> isIC2Mode, b -> isIC2Mode = b));
+ screenElements.widget(
+ new FakeSyncWidget.BooleanSyncer(
+ () -> this.mode == EIGModes.IC2,
+ b -> this.mode = b ? EIGModes.IC2 : EIGModes.Normal));
screenElements.widget(new FakeSyncWidget<>(() -> {
HashMap<ItemStack, Double> ret = new HashMap<>();
- HashMap<String, Double> dropProgress = new HashMap<>();
- for (Map.Entry<String, Double> drop : dropprogress.entrySet()) {
- dropProgress.merge(drop.getKey(), drop.getValue(), Double::sum);
+ for (Map.Entry<ItemStack, Double> drop : this.guiDropTracker.entrySet()) {
+ ret.merge(drop.getKey(), drop.getValue(), Double::sum);
}
- for (Map.Entry<String, Double> drop : dropProgress.entrySet()) {
- ret.put(GreenHouseSlot.dropstacks.get(drop.getKey()), drop.getValue());
- }
return ret;
- }, h -> GUIDropProgress = h, (buffer, h) -> {
+ }, h -> this.synchedGUIDropTracker = h, (buffer, h) -> {
buffer.writeVarIntToBuffer(h.size());
for (Map.Entry<ItemStack, Double> itemStackDoubleEntry : h.entrySet()) {
try {
@@ -941,33 +1227,28 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
List<String> info = new ArrayList<>(
Arrays.asList(
"Running in mode: " + EnumChatFormatting.GREEN
- + (setupphase == 0 ? (isIC2Mode ? "IC2 crops" : "Normal crops")
- : ("Setup mode " + (setupphase == 1 ? "(input)" : "(output)")))
+ + (this.setupPhase == 0 ? this.mode.getName()
+ : ("Setup mode " + (this.setupPhase == 1 ? "(input)" : "(output)")))
+ EnumChatFormatting.RESET,
- "Uses " + waterusage + "L/operation of water",
- "Uses " + weedexusage + "L/second of Weed-EX 9000",
- "Max slots: " + EnumChatFormatting.GREEN + this.mMaxSlots + EnumChatFormatting.RESET,
- "Used slots: " + ((mStorage.size() > mMaxSlots) ? EnumChatFormatting.RED : EnumChatFormatting.GREEN)
- + this.mStorage.size()
+ "Uses " + waterUsage + "L/operation of water",
+ "Uses " + weedEXUsage + "L/second of Weed-EX 9000",
+ "Max slots: " + EnumChatFormatting.GREEN + this.maxSeedTypes + EnumChatFormatting.RESET,
+ "Used slots: "
+ + ((this.buckets.size() > maxSeedTypes) ? EnumChatFormatting.RED : EnumChatFormatting.GREEN)
+ + this.buckets.size()
+ EnumChatFormatting.RESET));
- HashMap<String, Integer> storageList = new HashMap<>();
- for (GreenHouseSlot greenHouseSlot : mStorage) {
- if (!greenHouseSlot.isValid) continue;
- StringBuilder a = new StringBuilder(
- EnumChatFormatting.GREEN + "x"
- + greenHouseSlot.input.stackSize
- + " "
- + greenHouseSlot.input.getDisplayName());
- if (this.isIC2Mode) {
- a.append(" | Humidity: ")
- .append(greenHouseSlot.noHumidity ? 0 : 12);
- }
- a.append(EnumChatFormatting.RESET);
- storageList.merge(a.toString(), 1, Integer::sum);
+ for (EIGBucket bucket : buckets) {
+ info.add(bucket.getInfoData());
+ }
+ if (this.buckets.size() > this.maxSeedTypes) {
+ info.add(
+ EnumChatFormatting.DARK_RED + "There are too many seed types inside to run!"
+ + EnumChatFormatting.RESET);
+ }
+ if (this.getTotalSeedCount() > this.maxSeedCount) {
+ info.add(
+ EnumChatFormatting.DARK_RED + "There are too many seeds inside to run!" + EnumChatFormatting.RESET);
}
- storageList.forEach((k, v) -> info.add("x" + v + " " + k));
- if (mStorage.size() > mMaxSlots) info
- .add(EnumChatFormatting.DARK_RED + "There are too many crops inside to run !" + EnumChatFormatting.RESET);
info.addAll(Arrays.asList(super.getInfoData()));
return info.toArray(new String[0]);
}
@@ -999,548 +1280,5 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse
return new ITexture[] { Textures.BlockIcons.getCasingTextureForId(CASING_INDEX) };
}
- private boolean addCrop(ItemStack input, int slot, boolean simulate) {
- if (!isIC2Mode && !simulate)
- for (GreenHouseSlot g : mStorage) if (g.input.stackSize < 64 && GT_Utility.areStacksEqual(g.input, input)) {
- g.addAll(
- this.getBaseMetaTileEntity()
- .getWorld(),
- input);
- if (input.stackSize == 0) return true;
- }
- GreenHouseSlot h = new GreenHouseSlot(this, simulate ? input.copy() : input, isIC2Mode, isNoHumidity);
- if (h.isValid) {
- if (!simulate) {
- if (slot == -1) mStorage.add(h);
- else mStorage.add(slot, h);
- }
- return true;
- }
- return false;
- }
-
- private ItemStack addCrop(ItemStack input) {
- if (addCrop(input, -1, false)) return input;
- return null;
- }
-
- final Map<String, Double> dropprogress = new HashMap<>();
-
- public static class GreenHouseSlot extends InventoryCrafting {
-
- private static final int NUMBER_OF_GENERATIONS_TO_MAKE = 10;
-
- final ItemStack input;
- Block crop;
- ArrayList<ItemStack> customDrops = null;
- ItemStack undercrop = null;
- List<ItemStack> drops;
- public boolean isValid;
- boolean isIC2Crop;
- boolean noHumidity;
- int growthticks;
- List<List<ItemStack>> generations;
-
- Random rn;
- IRecipe recipe;
- ItemStack recipeInput;
-
- int optimalgrowth = 7;
-
- boolean needsreplanting = true;
-
- static final GreenHouseWorld fakeworld = new GreenHouseWorld(5, 5, 5);
-
- public NBTTagCompound toNBTTagCompound() {
- NBTTagCompound aNBT = new NBTTagCompound();
- aNBT.setTag("input", writeItemStackToNBT(input));
- aNBT.setBoolean("isValid", isValid);
- aNBT.setBoolean("isIC2Crop", isIC2Crop);
- if (!isIC2Crop) {
- aNBT.setInteger("crop", Block.getIdFromBlock(crop));
- if (customDrops != null && customDrops.size() > 0) {
- aNBT.setInteger("customDropsCount", customDrops.size());
- for (int i = 0; i < customDrops.size(); i++)
- aNBT.setTag("customDrop." + i, writeItemStackToNBT(customDrops.get(i)));
- }
- aNBT.setInteger("dropscount", drops.size());
- for (int i = 0; i < drops.size(); i++) aNBT.setTag("drop." + i, writeItemStackToNBT(drops.get(i)));
- aNBT.setInteger("optimalgrowth", optimalgrowth);
- aNBT.setBoolean("needsreplanting", needsreplanting);
- } else {
- if (undercrop != null) aNBT.setTag("undercrop", writeItemStackToNBT(undercrop));
- aNBT.setInteger("generationscount", generations.size());
- for (int i = 0; i < generations.size(); i++) {
- aNBT.setInteger(
- "generation." + i + ".count",
- generations.get(i)
- .size());
- for (int j = 0; j < generations.get(i)
- .size(); j++)
- aNBT.setTag(
- "generation." + i + "." + j,
- writeItemStackToNBT(
- generations.get(i)
- .get(j)));
- }
- aNBT.setInteger("growthticks", growthticks);
- aNBT.setBoolean("noHumidity", noHumidity);
- }
- return aNBT;
- }
-
- public GreenHouseSlot(NBTTagCompound aNBT) {
- super(null, 3, 3);
- isIC2Crop = aNBT.getBoolean("isIC2Crop");
- isValid = aNBT.getBoolean("isValid");
- input = readItemStackFromNBT(aNBT.getCompoundTag("input"));
- if (!isIC2Crop) {
- crop = Block.getBlockById(aNBT.getInteger("crop"));
- if (aNBT.hasKey("customDropsCount")) {
- int imax = aNBT.getInteger("customDropsCount");
- customDrops = new ArrayList<>(imax);
- for (int i = 0; i < imax; i++)
- customDrops.add(readItemStackFromNBT(aNBT.getCompoundTag("customDrop." + i)));
- }
- drops = new ArrayList<>();
- for (int i = 0; i < aNBT.getInteger("dropscount"); i++)
- drops.add(readItemStackFromNBT(aNBT.getCompoundTag("drop." + i)));
- optimalgrowth = aNBT.getInteger("optimalgrowth");
- if (optimalgrowth == 0) optimalgrowth = 7;
- if (aNBT.hasKey("needsreplanting")) needsreplanting = aNBT.getBoolean("needsreplanting");
- } else {
- if (aNBT.hasKey("undercrop")) undercrop = readItemStackFromNBT(aNBT.getCompoundTag("undercrop"));
- generations = new ArrayList<>();
- for (int i = 0; i < aNBT.getInteger("generationscount"); i++) {
- generations.add(new ArrayList<>());
- for (int j = 0; j < aNBT.getInteger("generation." + i + ".count"); j++) generations.get(i)
- .add(readItemStackFromNBT(aNBT.getCompoundTag("generation." + i + "." + j)));
- }
- growthticks = aNBT.getInteger("growthticks");
- noHumidity = aNBT.getBoolean("noHumidity");
- rn = new Random();
- }
- }
-
- public boolean addAll(World world, ItemStack input) {
- if (!GT_Utility.areStacksEqual(this.input, input)) return false;
- if (this.input.stackSize == 64) return false;
- int toconsume = Math.min(64 - this.input.stackSize, input.stackSize);
- int left = addDrops(world, toconsume);
- input.stackSize -= toconsume - left;
- this.input.stackSize += toconsume - left;
- return left == 0;
- }
-
- public boolean findCropRecipe(World world) {
- if (recipe != null) return true;
- out: for (ItemStack drop : drops) {
- recipeInput = drop;
- for (int j = 0; j < CraftingManager.getInstance()
- .getRecipeList()
- .size(); j++) {
- recipe = (IRecipe) CraftingManager.getInstance()
- .getRecipeList()
- .get(j);
- if (recipe.matches(this, world)
- && GT_Utility.areStacksEqual(recipe.getCraftingResult(this), input)) {
- break out;
- } else recipe = null;
- }
- }
- return recipe != null;
- }
-
- @Override
- public ItemStack getStackInSlot(int p_70301_1_) {
- if (p_70301_1_ == 0) return recipeInput.copy();
- return null;
- }
-
- @Override
- public ItemStack getStackInSlotOnClosing(int par1) {
- return null;
- }
-
- @Override
- public ItemStack decrStackSize(int par1, int par2) {
- return null;
- }
-
- @SuppressWarnings("EmptyMethod")
- @Override
- public void setInventorySlotContents(int par1, ItemStack par2ItemStack) {}
-
- public GreenHouseSlot(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity, ItemStack input, boolean IC2,
- boolean noHumidity) {
- super(null, 3, 3);
- World world = tileEntity.getBaseMetaTileEntity()
- .getWorld();
- this.input = input.copy();
- this.isValid = false;
- if (IC2) {
- GreenHouseSlotIC2(tileEntity, world, input, noHumidity);
- return;
- }
- Item i = input.getItem();
- Block b = null;
- boolean detectedCustomHandler = false;
- // Custom handlers
- // FLOWERS //
- Block bb = Block.getBlockFromItem(i);
- if (bb == Blocks.air) bb = null;
- if (bb instanceof BlockFlower) {
- detectedCustomHandler = true;
- needsreplanting = false;
- customDrops = new ArrayList<>(Collections.singletonList(input.copy()));
- customDrops.get(0).stackSize = 1;
- }
- if (!detectedCustomHandler) {
- if (i instanceof IPlantable) {
- if (i instanceof ItemSeeds) b = ((ItemSeeds) i).getPlant(world, 0, 0, 0);
- else if (i instanceof ItemSeedFood) b = ((ItemSeedFood) i).getPlant(world, 0, 0, 0);
- } else {
- if (i == Items.reeds) b = Blocks.reeds;
- else {
- b = Block.getBlockFromItem(i);
- if (b != Blocks.cactus) return;
- }
- needsreplanting = false;
- }
- if (!(b instanceof IPlantable)) return;
- GameRegistry.UniqueIdentifier u = GameRegistry.findUniqueIdentifierFor(i);
- if (u != null && Objects.equals(u.modId, "Natura")) optimalgrowth = 8;
-
- if (b instanceof BlockStem) {
- fakeworld.block = null;
- try {
- b.updateTick(fakeworld, 5, 5, 5, fakeworld.rand);
- } catch (Exception e) {
- e.printStackTrace(System.err);
- }
- if (fakeworld.block == null) return;
- b = fakeworld.block;
- needsreplanting = false;
- }
- }
- crop = b;
- isIC2Crop = false;
- int toUse = Math.min(64, input.stackSize);
- if (addDrops(world, toUse) == 0 && !drops.isEmpty()) {
- input.stackSize -= toUse;
- this.input.stackSize = toUse;
- this.isValid = true;
- }
- }
-
- public void GreenHouseSlotIC2(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity, World world,
- ItemStack input, boolean noHumidity) {
- if (!ItemList.IC2_Crop_Seeds.isStackEqual(input, true, true)) return;
- this.isIC2Crop = true;
- this.noHumidity = noHumidity;
- recalculate(tileEntity, world);
- if (this.isValid) input.stackSize--;
- }
-
- private boolean setBlock(ItemStack a, int x, int y, int z, World world) {
- Item item = a.getItem();
- Block b = Block.getBlockFromItem(item);
- if (b == Blocks.air || !(item instanceof ItemBlock)) return false;
- short tDamage = (short) item.getDamage(a);
- if (item instanceof GT_Item_Ores && tDamage > 0) {
- if (!world.setBlock(
- x,
- y,
- z,
- b,
- GT_TileEntity_Ores.getHarvestData(
- tDamage,
- ((GT_Block_Ores_Abstract) b).getBaseBlockHarvestLevel(tDamage % 16000 / 1000)),
- 0)) {
- return false;
- }
- GT_TileEntity_Ores tTileEntity = (GT_TileEntity_Ores) world.getTileEntity(x, y, z);
- tTileEntity.mMetaData = tDamage;
- tTileEntity.mNatural = false;
- } else world.setBlock(x, y, z, b, tDamage, 0);
- return true;
- }
-
- public void recalculate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity, World world) {
- if (isIC2Crop) {
- CropCard cc = Crops.instance.getCropCard(input);
- this.input.stackSize = 1;
- NBTTagCompound nbt = input.getTagCompound();
- byte gr = nbt.getByte("growth");
- byte ga = nbt.getByte("gain");
- byte re = nbt.getByte("resistance");
- int[] abc = new int[] { 0, -2, 3 };
- int[] xyz = new int[] { 0, 0, 0 };
- tileEntity.getExtendedFacing()
- .getWorldOffset(abc, xyz);
- xyz[0] += tileEntity.getBaseMetaTileEntity()
- .getXCoord();
- xyz[1] += tileEntity.getBaseMetaTileEntity()
- .getYCoord();
- xyz[2] += tileEntity.getBaseMetaTileEntity()
- .getZCoord();
- boolean cheating = false;
- try {
- if (world.getBlock(xyz[0], xyz[1] - 2, xyz[2]) != GregTech_API.sBlockCasings4
- || world.getBlockMetadata(xyz[0], xyz[1] - 2, xyz[2]) != 1) {
- // no
- cheating = true;
- return;
- }
-
- world.setBlock(xyz[0], xyz[1], xyz[2], Block.getBlockFromItem(Ic2Items.crop.getItem()), 0, 0);
- TileEntity wte = world.getTileEntity(xyz[0], xyz[1], xyz[2]);
- if (!(wte instanceof TileEntityCrop)) {
- // should not be even possible
- return;
- }
- TileEntityCrop te = (TileEntityCrop) wte;
- te.ticker = 1; // don't even think about ticking once
- te.setCrop(cc);
-
- te.setGrowth(gr);
- te.setGain(ga);
- te.setResistance(re);
-
- if (noHumidity) te.humidity = 0;
- else {
- te.waterStorage = 200;
- te.humidity = te.updateHumidity();
- }
- te.airQuality = te.updateAirQuality();
- te.nutrients = te.updateNutrients();
-
- ItemStack tobeused = null;
-
- if (undercrop != null) setBlock(undercrop, xyz[0], xyz[1] - 2, xyz[2], world);
- else {
- te.setSize((byte) (cc.maxSize() - 1));
- if (!cc.canGrow(te)) {
- // needs special block
-
- boolean cangrow = false;
- ArrayList<ItemStack> inputs = tileEntity.getStoredInputs();
- for (ItemStack a : inputs) {
- if (a.stackSize <= 0) continue;
- if (!setBlock(a, xyz[0], xyz[1] - 2, xyz[2], world)) continue;
- if (!cc.canGrow(te)) continue;
- cangrow = true;
- undercrop = a.copy();
- undercrop.stackSize = 1;
- tobeused = a;
- break;
- }
-
- if (!cangrow) return;
- }
- }
-
- te.setSize((byte) cc.maxSize());
-
- if (!cc.canBeHarvested(te)) return;
-
- // GENERATE DROPS
- generations = new ArrayList<>();
- int afterHarvestCropSize = 0;
- out: for (int i = 0; i < NUMBER_OF_GENERATIONS_TO_MAKE; i++) // get 10 generations
- {
- ItemStack[] st = te.harvest_automated(false);
- afterHarvestCropSize = te.getSize();
- te.setSize((byte) cc.maxSize());
- if (st == null) continue;
- if (st.length == 0) continue;
- for (ItemStack s : st) if (s == null) continue out;
- generations.add(new ArrayList<>(Arrays.asList(st)));
- }
- if (generations.isEmpty()) return;
- rn = new Random();
-
- // CHECK GROWTH SPEED
-
- growthticks = 0;
-
- for (int i = afterHarvestCropSize; i < cc.maxSize(); i++) {
- te.setSize((byte) i);
- int grown = 0;
- do {
- int rate = te.calcGrowthRate();
- if (rate == 0) return;
- growthticks++;
- grown += rate;
- } while (grown < cc.growthDuration(te));
- }
-
- growthticks *= TileEntityCrop.tickRate;
- if (growthticks < 1) growthticks = 1;
-
- if (tobeused != null) tobeused.stackSize--;
-
- this.isValid = true;
- } catch (Exception e) {
- e.printStackTrace(System.err);
- } finally {
- if (!cheating) world.setBlock(xyz[0], xyz[1] - 2, xyz[2], GregTech_API.sBlockCasings4, 1, 0);
- world.setBlockToAir(xyz[0], xyz[1], xyz[2]);
- }
- } else {
- drops = new ArrayList<>();
- addDrops(world, input.stackSize);
- }
- }
-
- public List<ItemStack> getDrops() {
- return drops;
- }
-
- static final Map<String, ItemStack> dropstacks = new HashMap<>();
-
- public List<ItemStack> getIC2Drops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity,
- double timeelapsed) {
- int r = rn.nextInt(NUMBER_OF_GENERATIONS_TO_MAKE);
- if (generations.size() <= r) return new ArrayList<>();
- double growthPercent = (timeelapsed / (double) growthticks);
- List<ItemStack> generation = generations.get(r);
- List<ItemStack> copied = new ArrayList<>();
- for (ItemStack g : generation) copied.add(g.copy());
- for (ItemStack s : copied) {
- double pro = ((double) s.stackSize * growthPercent);
- s.stackSize = 1;
- tileEntity.dropprogress.merge(s.toString(), pro, Double::sum);
- if (!dropstacks.containsKey(s.toString())) dropstacks.put(s.toString(), s.copy());
- }
- copied.clear();
- for (Map.Entry<String, Double> entry : tileEntity.dropprogress.entrySet()) if (entry.getValue() >= 1d) {
- copied.add(
- dropstacks.get(entry.getKey())
- .copy());
- copied.get(copied.size() - 1).stackSize = entry.getValue()
- .intValue();
- entry.setValue(
- entry.getValue() - (double) entry.getValue()
- .intValue());
- }
- return copied;
- }
-
- public int addDrops(World world, int count) {
- if (drops == null) drops = new ArrayList<>();
- if (customDrops != null && !customDrops.isEmpty()) {
- @SuppressWarnings("unchecked")
- ArrayList<ItemStack> d = (ArrayList<ItemStack>) customDrops.clone();
- for (ItemStack x : drops) {
- for (Iterator<ItemStack> iterator = d.iterator(); iterator.hasNext();) {
- ItemStack y = iterator.next();
- if (GT_Utility.areStacksEqual(x, y)) {
- x.stackSize += y.stackSize * count;
- iterator.remove();
- }
- }
- }
- final int finalCount = count;
- d.forEach(stack -> {
- ItemStack i = stack.copy();
- i.stackSize *= finalCount;
- drops.add(i);
- });
- return 0;
- } else {
- if (crop == null) return count;
- for (int i = 0; i < count; i++) {
- List<ItemStack> d = crop.getDrops(world, 0, 0, 0, optimalgrowth, 0);
- for (ItemStack x : drops) for (ItemStack y : d) if (GT_Utility.areStacksEqual(x, y)) {
- x.stackSize += y.stackSize;
- y.stackSize = 0;
- }
- for (ItemStack x : d) if (x.stackSize > 0) drops.add(x.copy());
- }
- }
- if (!needsreplanting) return 0;
- for (int i = 0; i < drops.size(); i++) {
- if (GT_Utility.areStacksEqual(drops.get(i), input)) {
- int took = Math.min(drops.get(i).stackSize, count);
- drops.get(i).stackSize -= took;
- count -= took;
- if (drops.get(i).stackSize == 0) {
- drops.remove(i);
- i--;
- }
- if (count == 0) {
- return 0;
- }
- }
- }
- if (!findCropRecipe(world)) return count;
- int totake = count / recipe.getCraftingResult(this).stackSize + 1;
- for (int i = 0; i < drops.size(); i++) {
- if (GT_Utility.areStacksEqual(drops.get(i), recipeInput)) {
- int took = Math.min(drops.get(i).stackSize, totake);
- drops.get(i).stackSize -= took;
- totake -= took;
- if (drops.get(i).stackSize == 0) {
- drops.remove(i);
- i--;
- }
- if (totake == 0) {
- return 0;
- }
- }
- }
- return count;
- }
- }
-
- private static class GreenHouseWorld extends GT_DummyWorld {
-
- public int x, y, z, meta = 0;
- public Block block;
-
- GreenHouseWorld(int x, int y, int z) {
- super();
- this.x = x;
- this.y = y;
- this.z = z;
- this.rand = new GreenHouseRandom();
- }
-
- @Override
- public int getBlockMetadata(int aX, int aY, int aZ) {
- if (aX == x && aY == y && aZ == z) return 7;
- return 0;
- }
-
- @Override
- public Block getBlock(int aX, int aY, int aZ) {
- if (aY == y - 1) return Blocks.farmland;
- return Blocks.air;
- }
-
- @Override
- public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) {
- return 10;
- }
-
- @Override
- public boolean setBlock(int aX, int aY, int aZ, Block aBlock, int aMeta, int aFlags) {
- if (aBlock == Blocks.air) return false;
- if (aX == x && aY == y && aZ == z) return false;
- block = aBlock;
- meta = aMeta;
- return true;
- }
- }
-
- private static class GreenHouseRandom extends Random {
-
- private static final long serialVersionUID = -387271808935248890L;
-
- @Override
- public int nextInt(int bound) {
- return 0;
- }
- }
+ // endregion ui
}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java
new file mode 100644
index 0000000000..1c5588c335
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java
@@ -0,0 +1,73 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockFlower;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGFlowerBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGFlowerBucket.Factory();
+ private static final String NBT_IDENTIFIER = "FLOWER";
+ private static final int REVISION_NUMBER = 0;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a flower, reed or cacti. They all drop their source item multiplied by their seed count
+ Item item = input.getItem();
+ Block block = Block.getBlockFromItem(item);
+ if (item != Items.reeds && block != Blocks.cactus && !(block instanceof BlockFlower)) return null;
+ return new EIGFlowerBucket(input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGFlowerBucket(nbt);
+ }
+ }
+
+ private EIGFlowerBucket(ItemStack input) {
+ super(input, 1, null);
+ }
+
+ private EIGFlowerBucket(NBTTagCompound nbt) {
+ super(nbt);
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ tracker.addDrop(this.seed, this.seedCount * multiplier);
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ return this.isValid();
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java
new file mode 100644
index 0000000000..7daa524d5d
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java
@@ -0,0 +1,905 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockLiquid;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.oredict.OreDictionary;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.ItemList;
+import gregtech.common.blocks.GT_Block_Ores_Abstract;
+import gregtech.common.blocks.GT_Item_Ores;
+import gregtech.common.blocks.GT_TileEntity_Ores;
+import ic2.api.crops.CropCard;
+import ic2.api.crops.Crops;
+import ic2.core.Ic2Items;
+import ic2.core.crop.CropStickreed;
+import ic2.core.crop.IC2Crops;
+import ic2.core.crop.TileEntityCrop;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGIC2Bucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGIC2Bucket.Factory();
+ private static final String NBT_IDENTIFIER = "IC2";
+ private static final int REVISION_NUMBER = 0;
+
+ // region crop simulation variables
+
+ private final static int NUMBER_OF_DROPS_TO_SIMULATE = 1000;
+ // nutrient factors
+ /**
+ * Set to true if you want to assume the crop is on wet farmland for a +2 bonus to nutrients
+ */
+ private static final boolean IS_ON_WET_FARMLAND = true;
+ /**
+ * The amount of water stored in the crop stick when hydration is turned on.
+ * bounds of 0 to 200 inclusive
+ */
+ private static final int WATER_STORAGE_VALUE = 200;
+ // nutrient factors
+ /**
+ * The number of blocks of dirt we assume are under. Subtract 1 if we have a block under our crop.
+ * bounds of 0 to 3, inclusive
+ */
+ private static final int NUMBER_OF_DIRT_BLOCKS_UNDER = 0;
+ /**
+ * The amount of fertilizer stored in the crop stick
+ * bounds of 0 to 200, inclusive
+ */
+ private static final int FERTILIZER_STORAGE_VALUE = 0;
+ // air quality factors
+ /**
+ * How many blocks in a 3x3 area centered on the crop do not contain solid blocks or other crops.
+ * Max value is 8 because the crop always counts itself.
+ * bound of 0-8 inclusive
+ */
+ private static final int CROP_OBSTRUCTION_VALUE = 5;
+ /**
+ * Being able to see the sky gives a +2 bonus to the air quality
+ */
+ private static final boolean CROP_CAN_SEE_SKY = false;
+
+ // endregion crop simulation variables
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a seed.
+ if (!ItemList.IC2_Crop_Seeds.isStackEqual(input, true, true)) return null;
+ if (!input.hasTagCompound()) return null;
+ // Validate that stat nbt data exists.
+ NBTTagCompound nbt = input.getTagCompound();
+ if (!(nbt.hasKey("growth") && nbt.hasKey("gain") && nbt.hasKey("resistance"))) return null;
+
+ CropCard cc = IC2Crops.instance.getCropCard(input);
+ if (cc == null) return null;
+ return new EIGIC2Bucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGIC2Bucket(nbt);
+ }
+ }
+
+ public final boolean useNoHumidity;
+ /**
+ * The average amount of growth cycles needed to reach maturity.
+ */
+ private double growthTime = 0;
+ private EIGDropTable drops = new EIGDropTable();
+ private boolean isValid = false;
+
+ /**
+ * Used to migrate old EIG greenhouse slots to the new bucket system, needs custom handling as to not void the
+ * support blocks.
+ *
+ * @implNote DOES NOT VALIDATE THE CONTENTS OF THE BUCKET, YOU'LL HAVE TO REVALIDATE WHEN THE WORLD IS LOADED.
+ *
+ * @param seed The item stack for the item that served as the seed before
+ * @param count The number of seed in the bucket
+ * @param supportBlock The block that goes under the bucket
+ * @param useNoHumidity Whether to use no humidity in growth speed calculations.
+ */
+ public EIGIC2Bucket(ItemStack seed, int count, ItemStack supportBlock, boolean useNoHumidity) {
+ super(seed, count, supportBlock == null ? null : new ItemStack[] { supportBlock });
+ this.useNoHumidity = useNoHumidity;
+ // revalidate me
+ this.isValid = false;
+ }
+
+ private EIGIC2Bucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack seed) {
+ super(seed, 1, null);
+ this.useNoHumidity = greenhouse.isInNoHumidityMode();
+ this.recalculateDrops(greenhouse);
+ }
+
+ private EIGIC2Bucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.useNoHumidity = nbt.getBoolean("useNoHumidity");
+ // If the invalid key exists then drops and growth time haven't been saved
+ if (!nbt.hasKey("invalid")) {
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.growthTime = nbt.getDouble("growthTime");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && this.growthTime > 0 && !this.drops.isEmpty();
+ }
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setBoolean("useNoHumidity", this.useNoHumidity);
+ if (this.isValid) {
+ nbt.setTag("drops", this.drops.save());
+ nbt.setDouble("growthTime", this.growthTime);
+ } else {
+ nbt.setBoolean("invalid", true);
+ }
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ // abort early if the bucket is invalid
+ if (!this.isValid()) return;
+ // else apply drops to tracker
+ double growthPercent = multiplier / (this.growthTime * TileEntityCrop.tickRate);
+ if (this.drops != null) {
+ this.drops.addTo(tracker, this.seedCount * growthPercent);
+ }
+ }
+
+ @Override
+ protected void getAdditionalInfoData(StringBuilder sb) {
+ sb.append(" | Humidity: ");
+ sb.append(this.useNoHumidity ? "Off" : "On");
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ /**
+ * (Re-)calculates the pre-generated drop table for this bucket.
+ *
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that contains this bucket.
+ */
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ World world = greenhouse.getBaseMetaTileEntity()
+ .getWorld();
+ int[] abc = new int[] { 0, -2, 3 };
+ int[] xyz = new int[] { 0, 0, 0 };
+ greenhouse.getExtendedFacing()
+ .getWorldOffset(abc, xyz);
+ xyz[0] += greenhouse.getBaseMetaTileEntity()
+ .getXCoord();
+ xyz[1] += greenhouse.getBaseMetaTileEntity()
+ .getYCoord();
+ xyz[2] += greenhouse.getBaseMetaTileEntity()
+ .getZCoord();
+ boolean cheating = false;
+ FakeTileEntityCrop crop;
+ try {
+ if (world.getBlock(xyz[0], xyz[1] - 2, xyz[2]) != GregTech_API.sBlockCasings4
+ || world.getBlockMetadata(xyz[0], xyz[1] - 2, xyz[2]) != 1) {
+ // no
+ cheating = true;
+ return;
+ }
+
+ // instantiate the TE in which we grow the seed.
+ crop = new FakeTileEntityCrop(this, greenhouse, xyz);
+ if (!crop.isValid) return;
+ CropCard cc = crop.getCrop();
+
+ // region can grow checks
+
+ // Check if we can put the current block under the soil.
+ if (this.supportItems != null && this.supportItems.length == 1 && this.supportItems[0] != null) {
+ if (!setBlock(this.supportItems[0], xyz[0], xyz[1] - 2, xyz[2], world)) {
+ return;
+ }
+ // update nutrients if we need a block under.
+ crop.updateNutrientsForBlockUnder();
+ }
+
+ // Check if the crop has a chance to die in the current environment
+ if (calcAvgGrowthRate(crop, cc, 0) < 0) return;
+ // Check if the crop has a chance to grow in the current environment.
+ if (calcAvgGrowthRate(crop, cc, 6) <= 0) return;
+
+ ItemStack blockInputStackToConsume = null;
+ if (!crop.canMature()) {
+ // If the block we have in storage no longer functions, we are no longer valid, the seed and block
+ // should be ejected if possible.
+ if (this.supportItems != null) return;
+ // assume we need a block under the farmland/fertilized dirt and update nutrients accordingly
+ crop.updateNutrientsForBlockUnder();
+ // Try to find the needed block in the inputs
+ boolean canGrow = false;
+ ArrayList<ItemStack> inputs = greenhouse.getStoredInputs();
+ for (ItemStack potentialBlock : inputs) {
+ // if the input can't be placed in the world skip to the next input
+ if (potentialBlock == null || potentialBlock.stackSize <= 0) continue;
+ if (!setBlock(potentialBlock, xyz[0], xyz[1] - 2, xyz[2], world)) continue;
+ // check if the crop can grow with the block under it.
+ if (!crop.canMature()) continue;
+ // If we don't have enough blocks to consume, abort.
+ if (this.seedCount > potentialBlock.stackSize) return;
+ canGrow = true;
+ blockInputStackToConsume = potentialBlock;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlock.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ break;
+ }
+
+ if (!canGrow) return;
+ }
+
+ // check if the crop does a block under check and try to put a requested block if possible
+ if (this.supportItems == null) {
+ // some crops get increased outputs if a specific block is under them.
+ cc.getGain(crop);
+ if (crop.hasRequestedBlockUnder()) {
+ ArrayList<ItemStack> inputs = greenhouse.getStoredInputs();
+ boolean keepLooking = !inputs.isEmpty();
+ if (keepLooking && !crop.reqBlockOreDict.isEmpty()) {
+ oreDictLoop: for (String reqOreDictName : crop.reqBlockOreDict) {
+ if (reqOreDictName == null || OreDictionary.doesOreNameExist(reqOreDictName)) continue;
+ int oreId = OreDictionary.getOreID(reqOreDictName);
+ for (ItemStack potentialBlock : inputs) {
+ if (potentialBlock == null || potentialBlock.stackSize <= 0) continue;
+ for (int inputOreId : OreDictionary.getOreIDs(potentialBlock)) {
+ if (inputOreId != oreId) continue;
+ blockInputStackToConsume = potentialBlock;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlock.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ keepLooking = false;
+ crop.updateNutrientsForBlockUnder();
+ break oreDictLoop;
+ }
+ }
+ }
+ }
+ if (keepLooking && !crop.reqBlockSet.isEmpty()) {
+ blockLoop: for (Block reqBlock : crop.reqBlockSet) {
+ if (reqBlock == null || reqBlock instanceof BlockLiquid) continue;
+ for (ItemStack potentialBlockStack : inputs) {
+ // TODO: figure out a way to handle liquid block requirements
+ // water lilly looks for water and players don't really have access to those.
+ if (potentialBlockStack == null || potentialBlockStack.stackSize <= 0) continue;
+ // check if it places a block that is equal to the the one we are looking for
+ Block inputBlock = Block.getBlockFromItem(potentialBlockStack.getItem());
+ if (inputBlock != reqBlock) continue;
+ blockInputStackToConsume = potentialBlockStack;
+ // Don't consume the block just yet, we do that once everything is valid.
+ ItemStack newSupport = potentialBlockStack.copy();
+ newSupport.stackSize = 1;
+ this.supportItems = new ItemStack[] { newSupport };
+ keepLooking = false;
+ crop.updateNutrientsForBlockUnder();
+ break blockLoop;
+ }
+ }
+ }
+ }
+ }
+
+ // check if the crop can be harvested at its max size
+ // Eg: the Eating plant cannot be harvested at its max size of 6, only 4 or 5 can
+ crop.setSize((byte) cc.maxSize());
+ if (!cc.canBeHarvested(crop)) return;
+
+ // endregion can grow checks
+
+ // region drop rate calculations
+
+ // PRE CALCULATE DROP RATES
+ // TODO: Add better loot table handling for crops like red wheat
+ // berries, etc.
+ EIGDropTable drops = new EIGDropTable();
+ // Multiply drop sizes by the average number drop rounds per harvest.
+ double avgDropRounds = getRealAverageDropRounds(crop, cc);
+ double avgStackIncrease = getRealAverageDropIncrease(crop, cc);
+ HashMap<Integer, Integer> sizeAfterHarvestFrequencies = new HashMap<>();
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ // try generating some loot drop
+ ItemStack drop = cc.getGain(crop);
+ if (drop == null || drop.stackSize <= 0) continue;
+ sizeAfterHarvestFrequencies.merge((int) cc.getSizeAfterHarvest(crop), 1, Integer::sum);
+
+ // Merge the new drop with the current loot table.
+ double avgAmount = (drop.stackSize + avgStackIncrease) * avgDropRounds;
+ drops.addDrop(drop, avgAmount / NUMBER_OF_DROPS_TO_SIMULATE);
+ }
+ if (drops.isEmpty()) return;
+
+ // endregion drop rate calculations
+
+ // region growth time calculation
+
+ // Just doing average(ceil(stageGrowth/growthSpeed)) isn't good enough it's off by as much as 20%
+ double avgGrowthCyclesToHarvest = calcRealAvgGrowthRate(crop, cc, sizeAfterHarvestFrequencies);
+ if (avgGrowthCyclesToHarvest <= 0) {
+ return;
+ }
+
+ // endregion growth time calculation
+
+ // Consume new under block if necessary
+ if (blockInputStackToConsume != null) blockInputStackToConsume.stackSize -= this.seedCount;
+ // We are good return success
+ this.growthTime = avgGrowthCyclesToHarvest;
+ this.drops = drops;
+ this.isValid = true;
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ } finally {
+ // always reset the world to it's original state
+ if (!cheating) world.setBlock(xyz[0], xyz[1] - 2, xyz[2], GregTech_API.sBlockCasings4, 1, 0);
+ // world.setBlockToAir(xyz[0], xyz[1], xyz[2]);
+ }
+ }
+
+ /**
+ * Attempts to place a block in the world, used for testing crop viability and drops.
+ *
+ * @param stack The {@link ItemStack} to place.
+ * @param x The x coordinate at which to place the block.
+ * @param y The y coordinate at which to place the block.
+ * @param z The z coordinate at which to place the block.
+ * @param world The world in which to place the block.
+ * @return true of a block was placed.
+ */
+ private static boolean setBlock(ItemStack stack, int x, int y, int z, World world) {
+ Item item = stack.getItem();
+ Block b = Block.getBlockFromItem(item);
+ if (b == Blocks.air || !(item instanceof ItemBlock)) return false;
+ short tDamage = (short) item.getDamage(stack);
+ if (item instanceof GT_Item_Ores && tDamage > 0) {
+ if (!world.setBlock(
+ x,
+ y,
+ z,
+ b,
+ GT_TileEntity_Ores.getHarvestData(
+ tDamage,
+ ((GT_Block_Ores_Abstract) b).getBaseBlockHarvestLevel(tDamage % 16000 / 1000)),
+ 0)) {
+ return false;
+ }
+ GT_TileEntity_Ores tTileEntity = (GT_TileEntity_Ores) world.getTileEntity(x, y, z);
+ tTileEntity.mMetaData = tDamage;
+ tTileEntity.mNatural = false;
+ } else world.setBlock(x, y, z, b, tDamage, 0);
+ return true;
+ }
+
+ // region drop rate calculations
+
+ /**
+ * Calculates the average number of separate item drops to be rolled per harvest using information obtained by
+ * decompiling IC2.
+ *
+ * @see TileEntityCrop#harvest_automated(boolean)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average number of drops to computer per harvest
+ */
+ private static double getRealAverageDropRounds(TileEntityCrop te, CropCard cc) {
+ // this should be ~99.995% accurate
+ double chance = (double) cc.dropGainChance() * Math.pow(1.03, te.getGain());
+ // this is essentially just performing an integration using the composite trapezoidal rule.
+ double min = -10, max = 10;
+ int steps = 10000;
+ double stepSize = (max - min) / steps;
+ double sum = 0;
+ for (int k = 1; k <= steps - 1; k++) {
+ sum += getWeightedDropChance(min + k * stepSize, chance);
+ }
+ double minVal = getWeightedDropChance(min, chance);
+ double maxVal = getWeightedDropChance(max, chance);
+ return stepSize * ((minVal + maxVal) / 2 + sum);
+ }
+
+ /**
+ * Evaluates the value of y for a standard normal distribution
+ *
+ * @param x The value of x to evaluate
+ * @return The value of y
+ */
+ private static double stdNormDistr(double x) {
+ return Math.exp(-0.5 * (x * x)) / SQRT2PI;
+ }
+
+ private static final double SQRT2PI = Math.sqrt(2.0d * Math.PI);
+
+ /**
+ * Calculates the weighted drop chance using
+ *
+ * @param x The value rolled by nextGaussian
+ * @param chance the base drop chance
+ * @return the weighted drop chance
+ */
+ private static double getWeightedDropChance(double x, double chance) {
+ return Math.max(0L, Math.round(x * chance * 0.6827d + chance)) * stdNormDistr(x);
+ }
+
+ /**
+ * Calculates the average drop of the stack size caused by seed's gain using information obtained by
+ * decompiling IC2.
+ *
+ * @see TileEntityCrop#harvest_automated(boolean)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average number of drops to computer per harvest
+ */
+ private static double getRealAverageDropIncrease(TileEntityCrop te, CropCard cc) {
+ // yes gain has the amazing ability to sometimes add 1 to your stack size!
+ return (te.getGain() + 1) / 100.0d;
+ }
+
+ // endregion drop rate calculations
+
+ // region growth time approximation
+
+ /**
+ * Calculates the average number growth cycles needed for a crop to grow to maturity.
+ *
+ * @see EIGIC2Bucket#calcAvgGrowthRate(TileEntityCrop, CropCard, int)
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @return The average growth rate as a floating point number
+ */
+ private static double calcRealAvgGrowthRate(TileEntityCrop te, CropCard cc,
+ HashMap<Integer, Integer> sizeAfterHarvestFrequencies) {
+ // Compute growth speeds.
+ int[] growthSpeeds = new int[7];
+ for (int i = 0; i < 7; i++) growthSpeeds[i] = calcAvgGrowthRate(te, cc, i);
+
+ // if it's stick reed, we know what the distribution should look like
+ if (cc.getClass() == CropStickreed.class) {
+ sizeAfterHarvestFrequencies.clear();
+ sizeAfterHarvestFrequencies.put(1, 1);
+ sizeAfterHarvestFrequencies.put(2, 1);
+ sizeAfterHarvestFrequencies.put(3, 1);
+ }
+
+ // Get the duration of all growth stages
+ int[] growthDurations = new int[cc.maxSize()];
+ // , index 0 is assumed to be 0 since stage 0 is usually impossible.
+ // The frequency table should prevent stage 0 from having an effect on the result.
+ growthDurations[0] = 0; // stage 0 doesn't usually exist.
+ for (byte i = 1; i < growthDurations.length; i++) {
+ te.setSize(i);
+ growthDurations[i] = cc.growthDuration(te);
+ }
+
+ return calcRealAvgGrowthRate(growthSpeeds, growthDurations, sizeAfterHarvestFrequencies);
+ }
+
+ /**
+ * Calculates the average number growth cycles needed for a crop to grow to maturity.
+ *
+ * @implNote This method is entirely self-contained and can therefore be unit tested.
+ *
+ * @param growthSpeeds The speeds at which the crop can grow.
+ * @param stageGoals The total to reach for each stage
+ * @param startStageFrequency How often the growth starts from a given stage
+ * @return The average growth rate as a floating point number
+ */
+ public static double calcRealAvgGrowthRate(int[] growthSpeeds, int[] stageGoals,
+ HashMap<Integer, Integer> startStageFrequency) {
+
+ // taking out the zero rolls out of the calculation tends to make the math more accurate for lower speeds.
+ int[] nonZeroSpeeds = Arrays.stream(growthSpeeds)
+ .filter(x -> x > 0)
+ .toArray();
+ int zeroRolls = growthSpeeds.length - nonZeroSpeeds.length;
+ if (zeroRolls >= growthSpeeds.length) return -1;
+
+ // compute stage lengths and stage frequencies
+ double[] avgCyclePerStage = new double[stageGoals.length];
+ double[] normalizedStageFrequencies = new double[stageGoals.length];
+ long frequenciesSum = startStageFrequency.values()
+ .parallelStream()
+ .mapToInt(x -> x)
+ .sum();
+ for (int i = 0; i < stageGoals.length; i++) {
+ avgCyclePerStage[i] = calcAvgCyclesToGoal(nonZeroSpeeds, stageGoals[i]);
+ normalizedStageFrequencies[i] = startStageFrequency.getOrDefault(i, 0) * stageGoals.length
+ / (double) frequenciesSum;
+ }
+
+ // Compute multipliers based on how often the growth starts at a given rate.
+ double[] frequencyMultipliers = new double[avgCyclePerStage.length];
+ Arrays.fill(frequencyMultipliers, 1.0d);
+ conv1DAndCopyToSignal(
+ frequencyMultipliers,
+ normalizedStageFrequencies,
+ new double[avgCyclePerStage.length],
+ 0,
+ frequencyMultipliers.length,
+ 0);
+
+ // apply multipliers to length
+ for (int i = 0; i < avgCyclePerStage.length; i++) avgCyclePerStage[i] *= frequencyMultipliers[i];
+
+ // lengthen average based on number of 0 rolls.
+ double average = Arrays.stream(avgCyclePerStage)
+ .average()
+ .orElse(-1);
+ if (average <= 0) return -1;
+ if (zeroRolls > 0) {
+ average = average / nonZeroSpeeds.length * growthSpeeds.length;
+ }
+
+ // profit
+ return average;
+ }
+
+ /**
+ * Computes the average number of rolls of an N sided fair dice with irregular number progressions needed to surpass
+ * a given total.
+ *
+ * @param speeds The speeds at which the crop grows.
+ * @param goal The total to match or surpass.
+ * @return The average number of rolls of speeds to meet or surpass the goal.
+ */
+ private static double calcAvgCyclesToGoal(int[] speeds, int goal) {
+ // even if the goal is 0, it will always take at least 1 cycle.
+ if (goal <= 0) return 1;
+ double mult = 1.0d;
+ int goalCap = speeds[speeds.length - 1] * 1000;
+ if (goal > goalCap) {
+ mult = (double) goal / goalCap;
+ goal = goalCap;
+ }
+ // condition start signal
+ double[] signal = new double[goal];
+ Arrays.fill(signal, 0);
+ signal[0] = 1;
+
+ // Create kernel out of our growth speeds
+ double[] kernel = tabulate(speeds, 1.0d / speeds.length);
+ double[] convolutionTarget = new double[signal.length];
+ LinkedList<Double> P = new LinkedList<Double>();
+
+ // Perform convolutions on the signal until it's too weak to be recognised.
+ double p, avgRolls = 1;
+ int iterNo = 0;
+ // 1e-1 is a threshold, you can increase it for to increase the accuracy of the output.
+ // 1e-1 is already accurate enough that any value beyond that is unwarranted.
+ int min = speeds[0];
+ int max = speeds[speeds.length - 1];
+ do {
+ avgRolls += p = conv1DAndCopyToSignal(signal, kernel, convolutionTarget, min, max, iterNo);
+ iterNo += 1;
+ } while (p >= 1e-1 / goal);
+ return avgRolls * mult;
+ }
+
+ /**
+ * Creates an array that corresponds to the amount of times a number appears in a list.
+ *
+ * Ex: {1,2,3,4} -> {0,1,1,1,1}, {0,2,2,4} -> {1,0,2,0,1}
+ *
+ * @param bin The number list to tabulate
+ * @param multiplier A multiplier to apply the output list
+ * @return The number to tabulate
+ */
+ private static double[] tabulate(int[] bin, double multiplier) {
+ double[] ret = new double[bin[bin.length - 1] + 1];
+ Arrays.fill(ret, 0);
+ for (int i : bin) ret[i] += multiplier;
+ return ret;
+ }
+
+ /**
+ * Computes a 1D convolution of a signal and stores the results in the signal array.
+ * Essentially performs `X <- convolve(X,rev(Y))[1:length(X)]` in R
+ *
+ * @param signal The signal to apply the convolution to.
+ * @param kernel The kernel to compute with.
+ * @param fixedLengthTarget A memory optimisation so we don't just create a ton of arrays since we overwrite it.
+ * Should be the same length as the signal.
+ */
+ private static double conv1DAndCopyToSignal(double[] signal, double[] kernel, double[] fixedLengthTarget,
+ int minValue, int maxValue, int iterNo) {
+ // for a 1d convolution we would usually use kMax = signal.length + kernel.length - 1
+ // but since we are directly applying our result to our signal, there is no reason to compute
+ // values where k > signal.length.
+ // we could probably run this loop in parallel.
+ double sum = 0;
+ int maxK = Math.min(signal.length, (iterNo + 1) * maxValue + 1);
+ int startAt = Math.min(signal.length, minValue * (iterNo + 1));
+ int k = Math.max(0, startAt - kernel.length);
+ for (; k < startAt; k++) fixedLengthTarget[k] = 0;
+ for (; k < maxK; k++) {
+ // I needs to be a valid index of the kernel.
+ fixedLengthTarget[k] = 0;
+ for (int i = Math.max(0, k - kernel.length + 1); i <= k; i++) {
+ double v = signal[i] * kernel[k - i];
+ sum += v;
+ fixedLengthTarget[k] += v;
+ }
+ }
+ System.arraycopy(fixedLengthTarget, 0, signal, 0, signal.length);
+ return sum;
+ }
+
+ /**
+ * Calculates the average growth rate of an ic2 crop using information obtained though decompiling IC2.
+ * Calls to random functions have been either replaced with customisable values or boundary tests.
+ *
+ * @see TileEntityCrop#calcGrowthRate()
+ * @param te The {@link TileEntityCrop} holding the crop
+ * @param cc The {@link CropCard} of the seed
+ * @param rngRoll The role for the base rng
+ * @return The amounts of growth point added to the growth progress in average every growth tick
+ */
+ private static int calcAvgGrowthRate(TileEntityCrop te, CropCard cc, int rngRoll) {
+ // the original logic uses IC2.random.nextInt(7)
+ int base = 3 + rngRoll + te.getGrowth();
+ int need = Math.max(0, (cc.tier() - 1) * 4 + te.getGrowth() + te.getGain() + te.getResistance());
+ int have = cc.weightInfluences(te, te.getHumidity(), te.getNutrients(), te.getAirQuality()) * 5;
+
+ if (have >= need) {
+ // The crop has a good enough environment to grow normally
+ return base * (100 + (have - need)) / 100;
+ } else {
+ // this only happens if we don't have enough
+ // resources to grow properly.
+ int neg = (need - have) * 4;
+
+ if (neg > 100) {
+ // a crop with a resistance 31 will never die since the original
+ // checks for `IC2.random.nextInt(32) > this.statResistance`
+ // so assume that the crop will eventually die if it doesn't
+ // have maxed out resistance stats. 0 means no growth this tick
+ // -1 means the crop dies.
+ return te.getResistance() >= 31 ? 0 : -1;
+ }
+ // else apply neg to base
+ return Math.max(0, base * (100 - neg) / 100);
+ }
+ }
+
+ // endregion growth time approximation
+
+ // region deterministic environmental calculations
+
+ /**
+ * Calculates the humidity at the location of the controller using information obtained by decompiling IC2.
+ * Returns 0 if the greenhouse is in no humidity mode.
+ *
+ * @see EIGIC2Bucket#IS_ON_WET_FARMLAND
+ * @see EIGIC2Bucket#WATER_STORAGE_VALUE
+ * @see TileEntityCrop#updateHumidity()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The humidity environmental value at the controller's location.
+ */
+ public static byte getHumidity(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, boolean useNoHumidity) {
+ if (useNoHumidity) return 0;
+ int value = Crops.instance.getHumidityBiomeBonus(
+ greenhouse.getBaseMetaTileEntity()
+ .getBiome());
+ if (IS_ON_WET_FARMLAND) value += 2;
+ // we add 2 if we have more than 5 water in storage
+ if (WATER_STORAGE_VALUE >= 5) value += 2;
+ // add 1 for every 25 water stored (max of 200
+ value += (WATER_STORAGE_VALUE + 24) / 25;
+ return (byte) value;
+ }
+
+ /**
+ * Calculates the nutrient value at the location of the controller using information obtained by decompiling IC2
+ *
+ * @see EIGIC2Bucket#NUMBER_OF_DIRT_BLOCKS_UNDER
+ * @see EIGIC2Bucket#FERTILIZER_STORAGE_VALUE
+ * @see TileEntityCrop#updateNutrients()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The nutrient environmental value at the controller's location.
+ */
+ public static byte getNutrients(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ int value = Crops.instance.getNutrientBiomeBonus(
+ greenhouse.getBaseMetaTileEntity()
+ .getBiome());
+ value += NUMBER_OF_DIRT_BLOCKS_UNDER;
+ value += (FERTILIZER_STORAGE_VALUE + 19) / 20;
+ return (byte) value;
+ }
+
+ /**
+ * Calculates the air quality at the location of the controller bucket using information obtained by decompiling IC2
+ *
+ * @see EIGIC2Bucket#CROP_OBSTRUCTION_VALUE
+ * @see EIGIC2Bucket#CROP_CAN_SEE_SKY
+ * @see TileEntityCrop#updateAirQuality()
+ * @param greenhouse The {@link GT_MetaTileEntity_ExtremeIndustrialGreenhouse} that holds the seed.
+ * @return The air quality environmental value at the controller's location.
+ */
+ public static byte getAirQuality(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ // clamp height bonus to 0-4, use the height of the crop itself
+ // TODO: check if we want to add the extra +2 for the actual height of the crop stick in the EIG.
+ int value = Math.max(
+ 0,
+ Math.min(
+ 4,
+ (greenhouse.getBaseMetaTileEntity()
+ .getYCoord() - 64) / 15));
+ // min value of fresh is technically 8 since the crop itself will count as an obstruction at xOff = 0, zOff = 0
+ value += CROP_OBSTRUCTION_VALUE / 2;
+ // you get a +2 bonus for being able to see the sky
+ if (CROP_CAN_SEE_SKY) value += 2;
+ return (byte) value;
+ }
+
+ // endregion deterministic environmental calculations
+
+ private static class FakeTileEntityCrop extends TileEntityCrop {
+
+ private boolean isValid;
+ public Set<Block> reqBlockSet = new HashSet<>();
+ public Set<String> reqBlockOreDict = new HashSet<>();
+ private int lightLevel = 15;
+
+ public FakeTileEntityCrop(EIGIC2Bucket bucket, GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse,
+ int[] xyz) {
+ super();
+ this.isValid = false;
+ this.ticker = 1;
+
+ // put seed in crop stick
+ CropCard cc = Crops.instance.getCropCard(bucket.seed);
+ this.setCrop(cc);
+ NBTTagCompound nbt = bucket.seed.getTagCompound();
+ this.setGrowth(nbt.getByte("growth"));
+ this.setGain(nbt.getByte("gain"));
+ this.setResistance(nbt.getByte("resistance"));
+ this.setWorldObj(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld());
+
+ this.xCoord = xyz[0];
+ this.yCoord = xyz[1];
+ this.zCoord = xyz[2];
+ this.blockType = Block.getBlockFromItem(Ic2Items.crop.getItem());
+ this.blockMetadata = 0;
+
+ this.waterStorage = bucket.useNoHumidity ? 0 : WATER_STORAGE_VALUE;
+ this.humidity = EIGIC2Bucket.getHumidity(greenhouse, bucket.useNoHumidity);
+ this.nutrientStorage = FERTILIZER_STORAGE_VALUE;
+ this.nutrients = EIGIC2Bucket.getNutrients(greenhouse);
+ this.airQuality = EIGIC2Bucket.getAirQuality(greenhouse);
+
+ this.isValid = true;
+ }
+
+ public boolean canMature() {
+ CropCard cc = this.getCrop();
+ this.size = cc.maxSize() - 1;
+ // try with a high light level
+ this.lightLevel = 15;
+ if (cc.canGrow(this)) return true;
+ // and then with a low light level.
+ this.lightLevel = 9;
+ return cc.canGrow(this);
+ }
+
+ @Override
+ public boolean isBlockBelow(Block reqBlock) {
+ this.reqBlockSet.add(reqBlock);
+ return super.isBlockBelow(reqBlock);
+ }
+
+ @Override
+ public boolean isBlockBelow(String oreDictionaryName) {
+ this.reqBlockOreDict.add(oreDictionaryName);
+ return super.isBlockBelow(oreDictionaryName);
+ }
+
+ // region environment simulation
+
+ @Override
+ public int getLightLevel() {
+ // 9 should allow most light dependent crops to grow
+ // the only exception I know of the eating plant which checks
+ return this.lightLevel;
+ }
+
+ @Override
+ public byte getHumidity() {
+ return this.humidity;
+ }
+
+ @Override
+ public byte updateHumidity() {
+ return this.humidity;
+ }
+
+ @Override
+ public byte getNutrients() {
+ return this.nutrients;
+ }
+
+ @Override
+ public byte updateNutrients() {
+ return this.nutrients;
+ }
+
+ @Override
+ public byte getAirQuality() {
+ return this.airQuality;
+ }
+
+ @Override
+ public byte updateAirQuality() {
+ return this.nutrients;
+ }
+
+ // endregion environment simulation
+
+ /**
+ * Updates the nutrient value based on the fact tha the crop needs a block under it.
+ */
+ public void updateNutrientsForBlockUnder() {
+ // -1 because the farm land is included in the root check.
+ if ((this.getCrop()
+ .getrootslength(this) - 1
+ - NUMBER_OF_DIRT_BLOCKS_UNDER) <= 0 && this.nutrients > 0) {
+ this.nutrients--;
+ }
+ }
+
+ /**
+ * Checks if the crop stick has requested a block to be under it yet.
+ *
+ * @return true if a block under check was made.
+ */
+ public boolean hasRequestedBlockUnder() {
+ return !this.reqBlockSet.isEmpty() || !this.reqBlockOreDict.isEmpty();
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java
new file mode 100644
index 0000000000..6342080722
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java
@@ -0,0 +1,77 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+import tb.common.block.BlockRainbowCactus;
+import tb.init.TBBlocks;
+
+// This is more of a proof of concept to show how to implement a custom handler.
+
+public class EIGRainbowCactusBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGRainbowCactusBucket.Factory();
+ private static final String NBT_IDENTIFIER = "TB:RAINCACTI";
+ private static final int REVISION_NUMBER = 0;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // check if input is rainbow cacti;
+ if (!(Block.getBlockFromItem(input.getItem()) instanceof BlockRainbowCactus)) return null;
+ return new EIGRainbowCactusBucket(input, 1);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGRainbowCactusBucket(nbt);
+ }
+
+ }
+
+ private final Random random = new Random();
+
+ public EIGRainbowCactusBucket(ItemStack seed, int seedCount) {
+ super(seed, seedCount, null);
+ }
+
+ public EIGRainbowCactusBucket(NBTTagCompound nbt) {
+ super(nbt);
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ return this.isValid();
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ // TODO: make the addDyeDropsToOutput static in TB.
+ ArrayList<ItemStack> drops = new ArrayList<>();
+ ((BlockRainbowCactus) TBBlocks.rainbowCactus).addDyeDropsToOutput(this.random, drops);
+ for (ItemStack drop : drops) {
+ tracker.addDrop(drop, drop.stackSize * multiplier * this.seedCount);
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java
new file mode 100644
index 0000000000..51b4a7162a
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java
@@ -0,0 +1,286 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+
+import net.minecraft.block.Block;
+import net.minecraft.init.Blocks;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemSeedFood;
+import net.minecraft.item.ItemSeeds;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.CraftingManager;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.common.IPlantable;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.GT_DummyWorld;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGSeedBucket extends EIGBucket {
+
+ public static final IEIGBucketFactory factory = new EIGSeedBucket.Factory();
+ private static final String NBT_IDENTIFIER = "SEED";
+ private static final int REVISION_NUMBER = 0;
+ private static final int NUMBER_OF_DROPS_TO_SIMULATE = 1000;
+ private static final int FORTUNE_LEVEL = 0;
+ private static final EIGSeedBucket.GreenHouseWorld fakeWorld = new EIGSeedBucket.GreenHouseWorld(5, 5, 5);
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ return new EIGSeedBucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGSeedBucket(nbt);
+ }
+
+ }
+
+ private boolean isValid = false;
+ private EIGDropTable drops = new EIGDropTable();
+
+ private EIGSeedBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack seed) {
+ super(seed, 1, null);
+ this.recalculateDrops(greenhouse);
+ }
+
+ private EIGSeedBucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && !this.drops.isEmpty();
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ nbt.setTag("drops", this.drops.save());
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ this.drops.addTo(tracker, multiplier * this.seedCount);
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ int optimalGrowthMetadata = 7;
+ // Get the relevant item and block for this item.
+ Item item = this.seed.getItem();
+ Block block;
+ if (!(item instanceof IPlantable)) return;
+ if (item instanceof ItemSeeds) {
+ block = ((ItemSeeds) item).getPlant(fakeWorld, 0, 0, 0);
+ } else if (item instanceof ItemSeedFood) {
+ block = ((ItemSeedFood) item).getPlant(fakeWorld, 0, 0, 0);
+ } else {
+ // We can't plant it, we can't handle it, get out.
+ return;
+ }
+
+ // Natura crops have an optimal harvest stage of 8.
+ GameRegistry.UniqueIdentifier u = GameRegistry.findUniqueIdentifierFor(item);
+ if (u != null && Objects.equals(u.modId, "Natura")) optimalGrowthMetadata = 8;
+
+ // Pre-Generate drops.
+ EIGDropTable drops = new EIGDropTable();
+ World world = greenhouse.getBaseMetaTileEntity()
+ .getWorld();
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ ArrayList<ItemStack> blockDrops = block.getDrops(world, 0, 0, 0, optimalGrowthMetadata, FORTUNE_LEVEL);
+ for (ItemStack drop : blockDrops) {
+ drops.addDrop(drop, drop.stackSize);
+ }
+ }
+
+ // reduce the number of drops to account for the seeds
+ if (!removeSeedFromDrops(world, drops, this.seed, NUMBER_OF_DROPS_TO_SIMULATE)) return;
+
+ // reduce drop count to account for the number of simulations
+ drops.entrySet()
+ .forEach(x -> x.setValue(x.getValue() / NUMBER_OF_DROPS_TO_SIMULATE));
+
+ // make sure we actually got a drop
+ if (drops.isEmpty()) return;
+
+ // and we are good, see ya.
+ this.drops = drops;
+ this.isValid = true;
+ }
+
+ private boolean removeSeedFromDrops(World world, EIGDropTable drops, ItemStack seed, int seedsToConsume) {
+ // make a safe copy of the seed just in case
+ ItemStack seedSafe = seed.copy();
+ seedSafe.stackSize = 1;
+ // first check if we dropped an item identical to our seed item.
+ int inputSeedDropCountAfterRemoval = (int) Math.round(drops.getItemAmount(seedSafe)) - seedsToConsume;
+ if (inputSeedDropCountAfterRemoval > 0) {
+ drops.setItemAmount(seedSafe, inputSeedDropCountAfterRemoval);
+ } else {
+ drops.removeItem(seedSafe);
+ }
+ // return true if we were able to find enough seeds in the drops.
+ if (inputSeedDropCountAfterRemoval >= 0) return true;
+
+ // else try to find items that can be crafted into the seed
+ int seedsToCraft = -inputSeedDropCountAfterRemoval;
+ IRecipe[] validRecipes = CraftingManager.getInstance()
+ .getRecipeList()
+ .parallelStream()
+ .filter(recipe -> GT_Utility.areStacksEqual(recipe.getRecipeOutput(), seed))
+ .toArray(IRecipe[]::new);
+
+ // if no recipes outputs the input seed, abort.
+ if (validRecipes.length == 0) return false;
+
+ // check the recipes we found for one that can consume our seed
+ for (Iterator<Map.Entry<ItemStack, Double>> dropIterator = drops.entrySet()
+ .iterator(); dropIterator.hasNext();) {
+ Map.Entry<ItemStack, Double> entry = dropIterator.next();
+ int inputCount = (int) Math.round(entry.getValue());
+ ItemStack input = entry.getKey()
+ .copy();
+ input.stackSize = 1;
+ EIGCraftingSeedFinder seedFinder = new EIGCraftingSeedFinder(input);
+ for (IRecipe recipe : validRecipes) {
+ if (recipe.matches(seedFinder, world)) {
+ // account for recipes that potentially drop more than 1 seed per input.
+ int outputsPerCraft = recipe.getCraftingResult(seedFinder).stackSize;
+ int craftableSeeds = outputsPerCraft * inputCount;
+ if (seedsToCraft >= craftableSeeds) {
+ // if the entire drop is consumed, just remove it from the list.
+ dropIterator.remove();
+ seedsToCraft -= craftableSeeds;
+ if (seedsToCraft <= 0) {
+ return true;
+ }
+ } else {
+ // else remove the right amount from the drop, and get out.
+ entry.setValue(entry.getValue() - (double) seedsToCraft / outputsPerCraft);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ class EIGCraftingSeedFinder extends InventoryCrafting {
+
+ public ItemStack recipeInput;
+
+ public EIGCraftingSeedFinder(ItemStack recipeInput) {
+ super(null, 3, 3);
+ this.recipeInput = recipeInput;
+ }
+
+ @Override
+ public ItemStack getStackInSlot(int p_70301_1_) {
+ if (p_70301_1_ == 0) return this.recipeInput.copy();
+ return null;
+ }
+
+ @Override
+ public ItemStack getStackInSlotOnClosing(int par1) {
+ return null;
+ }
+
+ @Override
+ public ItemStack decrStackSize(int par1, int par2) {
+ return null;
+ }
+
+ @SuppressWarnings("EmptyMethod")
+ @Override
+ public void setInventorySlotContents(int par1, ItemStack par2ItemStack) {}
+ }
+
+ private static class GreenHouseWorld extends GT_DummyWorld {
+
+ public int x, y, z, meta = 0;
+ public Block block;
+
+ GreenHouseWorld(int x, int y, int z) {
+ super();
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.rand = new EIGSeedBucket.GreenHouseRandom();
+ }
+
+ @Override
+ public int getBlockMetadata(int aX, int aY, int aZ) {
+ if (aX == x && aY == y && aZ == z) return 7;
+ return 0;
+ }
+
+ @Override
+ public Block getBlock(int aX, int aY, int aZ) {
+ if (aY == y - 1) return Blocks.farmland;
+ return Blocks.air;
+ }
+
+ @Override
+ public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) {
+ return 10;
+ }
+
+ @Override
+ public boolean setBlock(int aX, int aY, int aZ, Block aBlock, int aMeta, int aFlags) {
+ if (aBlock == Blocks.air) return false;
+ if (aX == x && aY == y && aZ == z) return false;
+ block = aBlock;
+ meta = aMeta;
+ return true;
+ }
+ }
+
+ private static class GreenHouseRandom extends Random {
+
+ private static final long serialVersionUID = -387271808935248890L;
+
+ @Override
+ public int nextInt(int bound) {
+ return 0;
+ }
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java
new file mode 100644
index 0000000000..e0ebcf8652
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java
@@ -0,0 +1,158 @@
+package kubatech.tileentity.gregtech.multiblock.eigbuckets;
+
+import java.util.ArrayList;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockStem;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.common.IPlantable;
+
+import kubatech.api.IBlockStemAccesor;
+import kubatech.api.eig.EIGBucket;
+import kubatech.api.eig.EIGDropTable;
+import kubatech.api.eig.IEIGBucketFactory;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGStemBucket extends EIGBucket {
+
+ public final static IEIGBucketFactory factory = new EIGStemBucket.Factory();
+ private static final String NBT_IDENTIFIER = "STEM";
+ private static final int REVISION_NUMBER = 0;
+ private final static int NUMBER_OF_DROPS_TO_SIMULATE = 100;
+
+ public static class Factory implements IEIGBucketFactory {
+
+ @Override
+ public String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public EIGBucket tryCreateBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ // Check if input is a flower, reed or cacti. They all drop their source item multiplied by their seed count
+ Item item = input.getItem();
+ if (!(item instanceof IPlantable)) return null;
+ Block block = ((IPlantable) item).getPlant(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ 0,
+ 0,
+ 0);
+ if (!(block instanceof BlockStem)) return null;
+ return new EIGStemBucket(greenhouse, input);
+ }
+
+ @Override
+ public EIGBucket restore(NBTTagCompound nbt) {
+ return new EIGStemBucket(nbt);
+ }
+ }
+
+ private boolean isValid = false;
+ private EIGDropTable drops = new EIGDropTable();
+
+ private EIGStemBucket(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input) {
+ super(input, 1, null);
+ recalculateDrops(greenhouse);
+ }
+
+ private EIGStemBucket(NBTTagCompound nbt) {
+ super(nbt);
+ this.drops = new EIGDropTable(nbt, "drops");
+ this.isValid = nbt.getInteger("version") == REVISION_NUMBER && !this.drops.isEmpty();
+ }
+
+ @Override
+ public NBTTagCompound save() {
+ NBTTagCompound nbt = super.save();
+ if (this.drops != null) {
+ nbt.setTag("drops", this.drops.save());
+ }
+ nbt.setInteger("version", REVISION_NUMBER);
+ return nbt;
+ }
+
+ @Override
+ protected String getNBTIdentifier() {
+ return NBT_IDENTIFIER;
+ }
+
+ @Override
+ public void addProgress(double multiplier, EIGDropTable tracker) {
+ if (!this.isValid()) return;
+ this.drops.addTo(tracker, multiplier * this.seedCount);
+ }
+
+ @Override
+ public boolean isValid() {
+ return super.isValid() && this.isValid;
+ }
+
+ @Override
+ public boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ recalculateDrops(greenhouse);
+ return this.isValid();
+ }
+
+ /**
+ * Attempts to predetermine what item the stem crop will drop.
+ *
+ * @param greenhouse The greenhouse that houses this bucket.
+ */
+ public void recalculateDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse) {
+ this.isValid = false;
+ Item item = this.seed.getItem();
+ if (!(item instanceof IPlantable)) return;
+ Block stemBlock = ((IPlantable) item).getPlant(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ 0,
+ 0,
+ 0);
+ if (!(stemBlock instanceof BlockStem)) return;
+ Block cropBlock = ((IBlockStemAccesor) stemBlock).getCropBlock();
+ if (cropBlock == null || cropBlock == Blocks.air) return;
+ // if we know some crops needs a specific metadata, remap here
+ int metadata = 0;
+
+ EIGDropTable drops = new EIGDropTable();
+
+ for (int i = 0; i < NUMBER_OF_DROPS_TO_SIMULATE; i++) {
+ // simulate 1 round of drops
+ ArrayList<ItemStack> blockDrops = cropBlock.getDrops(
+ greenhouse.getBaseMetaTileEntity()
+ .getWorld(),
+ greenhouse.getBaseMetaTileEntity()
+ .getXCoord(),
+ greenhouse.getBaseMetaTileEntity()
+ .getYCoord(),
+ greenhouse.getBaseMetaTileEntity()
+ .getZCoord(),
+ metadata,
+ 0);
+ if (blockDrops == null || blockDrops.isEmpty()) continue;
+ // if the droped item is a block that places itself, assume this is the only possible drop
+ // eg: pumpkin, redlon
+ if (i == 0 && blockDrops.size() == 1) {
+ ItemStack drop = blockDrops.get(0);
+ if (drop != null && drop.stackSize >= 1 && drop.getItem() == Item.getItemFromBlock(cropBlock)) {
+ drops.addDrop(drop, drop.stackSize);
+ break;
+ }
+ }
+ // else append all the drops
+ for (ItemStack drop : blockDrops) {
+ drops.addDrop(drop, drop.stackSize / (double) NUMBER_OF_DROPS_TO_SIMULATE);
+ }
+ }
+ // check that we did in fact drop something.s
+ if (drops.isEmpty()) return;
+
+ // all checks passed we are good to go
+ this.drops = drops;
+ this.isValid = true;
+ }
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java
new file mode 100644
index 0000000000..6c12373779
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java
@@ -0,0 +1,95 @@
+package kubatech.tileentity.gregtech.multiblock.eigmodes;
+
+import static kubatech.api.utils.StringUtils.voltageTooltipFormatted;
+import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_ACCELERATOR_TIER;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGIC2Mode extends EIGMode {
+
+ public static final EIGIC2Mode instance = new EIGIC2Mode();
+
+ @Override
+ public int getUIIndex() {
+ return 1;
+ }
+
+ @Override
+ public String getName() {
+ return "IC2";
+ }
+
+ @Override
+ public int getMinVoltageTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getMinGlassTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getStartingSlotCount() {
+ return 4;
+ }
+
+ @Override
+ public int getSlotPerTierMultiplier() {
+ return 4;
+ }
+
+ @Override
+ public int getSeedCapacityPerSlot() {
+ return 1;
+ }
+
+ @Override
+ public int getWeedEXMultiplier() {
+ return 5;
+ }
+
+ @Override
+ public int getMaxFertilizerUsagePerSeed() {
+ return 40;
+ }
+
+ @Override
+ public double getFertilizerBoost() {
+ return 0.1d;
+ }
+
+ @Override
+ public GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder) {
+ String minVoltageTier = voltageTooltipFormatted(this.getMinVoltageTier());
+ String minGlassTier = voltageTooltipFormatted(this.getMinGlassTier());
+
+ int acceleration = (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER);
+
+ double fertilizerBonusMultiplier = this.getFertilizerBoost() * 100;
+ String fertilizerBonus = String.format("%.0f%%", fertilizerBonusMultiplier);
+
+ return builder.addInfo("---------------------- IC2 CROPS ---------------------")
+ .addInfo("Minimal voltage tier: " + minVoltageTier)
+ .addInfo("Minimal glass tier: " + minGlassTier)
+ .addInfo("Starting with " + this.getStartingSlotCount() + " slot")
+ .addInfo(
+ "Every tier past " + minVoltageTier + ", slots are multiplied by " + this.getSlotPerTierMultiplier())
+ .addInfo("Every slot adds " + this.getSeedCapacityPerSlot() + " seed to the total seed capacity")
+ .addInfo("Process time: 5 sec")
+ .addInfo("All crops are accelerated by x" + acceleration + " times")
+ .addInfo("Can consume up to " + this.getMaxFertilizerUsagePerSeed() + " fertilizer per seed per cycle")
+ .addInfo("Boost per fertilizer: " + fertilizerBonus)
+ .addInfo("Weed-EX 9000 consumption is multiplied by " + this.getWeedEXMultiplier());
+ }
+
+ @Override
+ public int getSlotCount(int machineTier) {
+ int tierAboveMin = machineTier - this.getMinVoltageTier();
+ if (tierAboveMin < 0) return 0;
+ return 4 << (2 * (tierAboveMin));
+ }
+
+}
diff --git a/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java
new file mode 100644
index 0000000000..a1c5fcf393
--- /dev/null
+++ b/src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java
@@ -0,0 +1,91 @@
+package kubatech.tileentity.gregtech.multiblock.eigmodes;
+
+import static kubatech.api.utils.StringUtils.voltageTooltipFormatted;
+
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import kubatech.api.eig.EIGMode;
+import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse;
+
+public class EIGNormalMode extends EIGMode {
+
+ public static final EIGNormalMode instance = new EIGNormalMode();
+
+ @Override
+ public int getUIIndex() {
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return "normal";
+ }
+
+ @Override
+ public int getMinVoltageTier() {
+ return GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_REGULAR_MODE_MIN_TIER;
+ }
+
+ @Override
+ public int getMinGlassTier() {
+ return 0;
+ }
+
+ @Override
+ public int getStartingSlotCount() {
+ return 1;
+ }
+
+ @Override
+ public int getSlotPerTierMultiplier() {
+ return 2;
+ }
+
+ @Override
+ public int getSeedCapacityPerSlot() {
+ return 64;
+ }
+
+ @Override
+ public int getWeedEXMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public int getMaxFertilizerUsagePerSeed() {
+ return 2;
+ }
+
+ @Override
+ public double getFertilizerBoost() {
+ return 2.0d;
+ }
+
+ @Override
+ public GT_Multiblock_Tooltip_Builder addTooltipInfo(GT_Multiblock_Tooltip_Builder builder) {
+ String minVoltageTier = voltageTooltipFormatted(this.getMinVoltageTier());
+ String minVoltageTierMinus1 = voltageTooltipFormatted(this.getMinVoltageTier() - 1);
+
+ double fertilizerBonusMultiplier = this.getFertilizerBoost() * 100;
+ String fertilizerBonus = String.format("%.0f%%", fertilizerBonusMultiplier);
+
+ return builder.addInfo("-------------------- NORMAL CROPS --------------------")
+ .addInfo("Minimal voltage tier: " + minVoltageTier)
+ .addInfo("Starting with " + this.getStartingSlotCount() + " slot")
+ .addInfo(
+ "Every tier past " + minVoltageTier + ", slots are multiplied by " + this.getSlotPerTierMultiplier())
+ .addInfo("Every slot adds " + this.getSeedCapacityPerSlot() + " seed to the total seed capacity")
+ .addInfo("Base process time: 5 sec")
+ .addInfo("Process time is divided by number of tiers past " + minVoltageTierMinus1 + " (Minimum 1 sec)")
+ .addInfo("All crops are grown at the end of the operation")
+ .addInfo("Does not drop seeds")
+ .addInfo("Can consume up to " + this.getMaxFertilizerUsagePerSeed() + " fertilizer per seed per cycle")
+ .addInfo("Boost per fertilizer: " + fertilizerBonus);
+ }
+
+ @Override
+ public int getSlotCount(int machineTier) {
+ int tierAboveMin = machineTier - this.getMinVoltageTier();
+ if (tierAboveMin < 0) return 0;
+ return (1 << tierAboveMin);
+ }
+}
diff --git a/src/main/resources/assets/kubatech/lang/en_US.lang b/src/main/resources/assets/kubatech/lang/en_US.lang
index 217d438ce0..b7241bf371 100644
--- a/src/main/resources/assets/kubatech/lang/en_US.lang
+++ b/src/main/resources/assets/kubatech/lang/en_US.lang
@@ -32,7 +32,8 @@ GT5U.gui.text.EEC_invalidspawner=Invalid spawner!
GT5U.gui.text.EEC_peaceful=Inserted mob cannot spawn on peaceful mode!
GT5U.gui.text.MegaApiary_slotoverflow=Too much bees inside!
GT5U.gui.text.MegaApiary_noflowers=Missing flowers!
-GT5U.gui.text.EIG_slotoverflow=Too much crops inside!
+GT5U.gui.text.EIG_slotoverflow=Too many seed types inside!
+GT5U.gui.text.EIG_seedOverflow=Too many seeds inside!
GT5U.gui.text.EIG_missingwater=No water!
GT5U.gui.text.EIG_ic2glass=Insufficient glass tier!