diff options
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! |