diff options
Diffstat (limited to 'src/main/java/kubatech/tileentity/gregtech')
8 files changed, 2339 insertions, 916 deletions
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); + } +} |
