From 337594e83a74c432c140b3df3287575b81bce467 Mon Sep 17 00:00:00 2001 From: Raven Szewczyk Date: Thu, 30 May 2024 18:26:10 +0100 Subject: Complete backend rework of the EIG (#2616) * Complete backend rework of the EIG * Mergening Related Updates Also some loader references refactoring * fix (cherry picked from commit 7fd5d7417bddfb6e49ede3986d9a547f15b21289) * More Mergening fixes Updates the declaration of the stem mixin to match the new format. * Inline EIG IC2 bucket constants addresses: https://github.com/GTNewHorizons/GT5-Unofficial/pull/2616#discussion_r1620596497 * Fix Seed Removal in regular seed simulations Should address https://github.com/GTNewHorizons/GT5-Unofficial/pull/2616#discussion_r1620583338 --------- Co-authored-by: Guillaume Mercier <10gui-gui10@live.ca> Co-authored-by: Martin Robertz --- dependencies.gradle | 1 + .../java/kubatech/test/EIGTests.java | 227 +-- src/main/java/gregtech/mixin/Mixin.java | 7 +- .../mixins/early/minecraft/BlockStemMixin.java | 23 + src/main/java/kubatech/CommonProxy.java | 2 + .../java/kubatech/api/EIGDynamicInventory.java | 510 +++++++ src/main/java/kubatech/api/IBlockStemAccesor.java | 8 + src/main/java/kubatech/api/eig/EIGBucket.java | 247 +++ src/main/java/kubatech/api/eig/EIGDropTable.java | 224 +++ src/main/java/kubatech/api/eig/EIGMode.java | 154 ++ .../java/kubatech/api/eig/IEIGBucketFactory.java | 15 + src/main/java/kubatech/api/enums/EIGModes.java | 42 + .../java/kubatech/api/enums/EIGSetupPhase.java | 16 + .../kubatech/api/gui/AutoScalingStackSizeText.java | 72 + .../implementations/KubaTechGTMultiBlockBase.java | 6 +- src/main/java/kubatech/api/utils/StringUtils.java | 6 + .../java/kubatech/loaders/EIGBucketLoader.java | 27 + src/main/java/kubatech/loaders/RecipeLoader.java | 11 + ...MetaTileEntity_ExtremeIndustrialGreenhouse.java | 1570 ++++++++------------ .../multiblock/eigbuckets/EIGFlowerBucket.java | 73 + .../multiblock/eigbuckets/EIGIC2Bucket.java | 905 +++++++++++ .../eigbuckets/EIGRainbowCactusBucket.java | 77 + .../multiblock/eigbuckets/EIGSeedBucket.java | 286 ++++ .../multiblock/eigbuckets/EIGStemBucket.java | 158 ++ .../gregtech/multiblock/eigmodes/EIGIC2Mode.java | 95 ++ .../multiblock/eigmodes/EIGNormalMode.java | 91 ++ src/main/resources/assets/kubatech/lang/en_US.lang | 3 +- 27 files changed, 3791 insertions(+), 1065 deletions(-) create mode 100644 src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java create mode 100644 src/main/java/kubatech/api/EIGDynamicInventory.java create mode 100644 src/main/java/kubatech/api/IBlockStemAccesor.java create mode 100644 src/main/java/kubatech/api/eig/EIGBucket.java create mode 100644 src/main/java/kubatech/api/eig/EIGDropTable.java create mode 100644 src/main/java/kubatech/api/eig/EIGMode.java create mode 100644 src/main/java/kubatech/api/eig/IEIGBucketFactory.java create mode 100644 src/main/java/kubatech/api/enums/EIGModes.java create mode 100644 src/main/java/kubatech/api/enums/EIGSetupPhase.java create mode 100644 src/main/java/kubatech/api/gui/AutoScalingStackSizeText.java create mode 100644 src/main/java/kubatech/loaders/EIGBucketLoader.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGFlowerBucket.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGIC2Bucket.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGRainbowCactusBucket.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGSeedBucket.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigbuckets/EIGStemBucket.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGIC2Mode.java create mode 100644 src/main/java/kubatech/tileentity/gregtech/multiblock/eigmodes/EIGNormalMode.java diff --git a/dependencies.gradle b/dependencies.gradle index d4d2bdf0f5..04d535fce8 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -61,6 +61,7 @@ dependencies { compileOnlyApi("com.github.GTNewHorizons:Railcraft:9.15.8:dev") { transitive = false } compileOnly("TGregworks:TGregworks:1.7.10-GTNH-1.0.26:deobf") {transitive = false} + compileOnly("com.github.GTNewHorizons:ThaumicBases:1.6.4:dev") { transitive = false } compileOnly("com.github.GTNewHorizons:EnderCore:0.4.6:dev") { transitive = false } implementation("com.github.GTNewHorizons:Galacticraft:3.1.5-GTNH:dev") { transitive = false } implementation("com.github.GTNewHorizons:TinkersConstruct:1.12.2-GTNH:dev") diff --git a/src/functionalTest/java/kubatech/test/EIGTests.java b/src/functionalTest/java/kubatech/test/EIGTests.java index 1bb88814a4..7cdfa8dae4 100644 --- a/src/functionalTest/java/kubatech/test/EIGTests.java +++ b/src/functionalTest/java/kubatech/test/EIGTests.java @@ -20,15 +20,10 @@ package kubatech.test; -import static gregtech.api.util.GT_RecipeBuilder.MINUTES; -import static gregtech.api.util.GT_RecipeBuilder.SECONDS; -import static kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse.EIG_BALANCE_IC2_ACCELERATOR_TIER; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.fail; +import static gregtech.api.util.GT_RecipeBuilder.HOURS; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; -import java.util.Arrays; -import java.util.Comparator; import java.util.Map; import net.minecraft.block.Block; @@ -37,7 +32,6 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.ChatComponentText; import net.minecraft.world.MinecraftException; import net.minecraft.world.World; import net.minecraft.world.WorldProvider; @@ -45,6 +39,7 @@ import net.minecraft.world.WorldProviderSurface; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldSettings; import net.minecraft.world.WorldType; +import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.chunk.storage.IChunkLoader; import net.minecraft.world.storage.IPlayerFileData; import net.minecraft.world.storage.ISaveHandler; @@ -53,8 +48,6 @@ import net.minecraftforge.common.DimensionManager; import org.junit.jupiter.api.Test; -import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap; - import gregtech.api.GregTech_API; import gregtech.api.interfaces.tileentity.IGregTechTileEntity; import gregtech.common.blocks.GT_Item_Machines; @@ -62,13 +55,16 @@ import ic2.api.crops.CropCard; import ic2.api.crops.Crops; import ic2.core.Ic2Items; import ic2.core.crop.TileEntityCrop; +import ic2.core.item.ItemCropSeed; +import kubatech.api.eig.EIGDropTable; import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse; +import kubatech.tileentity.gregtech.multiblock.eigbuckets.EIGIC2Bucket; public class EIGTests { private static final int EIG_CONTROLLER_METADATA = 12_792; - private static final int NUMBER_OF_CROPS_PER_TEST = 90; - private static final int NUMBER_OF_TESTS_TO_DO = 30; + private static final int EIG_SIMULATION_TIME = 24 * HOURS; + private static final int NUMBER_OF_TESTS_TO_DO = 1000; static World myWorld; @@ -144,85 +140,73 @@ public class EIGTests { public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) { return 4; } + + @Override + public BiomeGenBase getBiomeGenForCoords(int x, int z) { + // give the environment a fighting chance of being bearable for crops + return BiomeGenBase.jungle; + } }; } } - private static int leftOverTicksFromRealRun = 0; - - ItemStackMap getRealDrops(TileEntityCrop cropTile, CropCard cc, int growth, int gain, int resistance) { + EIGDropTable getRealDrops(TileEntityCrop cropTile, CropCard cc, int growth, int gain, int resistance) { cropTile.setCrop(cc); cropTile.setGrowth((byte) growth); cropTile.setGain((byte) gain); cropTile.setResistance((byte) resistance); - cropTile.tick(); - - ItemStackMap expected = new ItemStackMap<>(); - - // run for 30 minutes - for (int k = 0; k < NUMBER_OF_CROPS_PER_TEST; k++) { - cropTile.ticker = 1; - cropTile.setSize((byte) cc.maxSize()); - cropTile.setSize(cc.getSizeAfterHarvest(cropTile)); - cropTile.growthPoints = 0; - int lastHarvestedAt = 0; - int i; - for (i = 0; i < (30 * MINUTES) * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER);) { - i += TileEntityCrop.tickRate; - cropTile.tick(); - if (!cc.canGrow(cropTile)) { - lastHarvestedAt = i; - ItemStack[] stacks = cropTile.harvest_automated(false); - for (ItemStack stack : stacks) { - expected.merge(stack, stack.stackSize, Integer::sum); - } + EIGDropTable expected = new EIGDropTable(); + byte startingHumidity = cropTile.humidity; + byte startingNutrients = cropTile.nutrients; + byte startingAirQuality = cropTile.airQuality; + int startingNutrientStorage = cropTile.nutrientStorage; + int startingWaterStorage = cropTile.waterStorage; + // reset the crop to it's stage after harvest + cropTile.setSize((byte) cc.maxSize()); + cropTile.setSize(cc.getSizeAfterHarvest(cropTile)); + for (int timeElapsed = 0; timeElapsed < EIG_SIMULATION_TIME; timeElapsed += TileEntityCrop.tickRate) { + // force reset the stats to max because the eig shouldn't make them change. + // some crops check water storage in the can grow and we are ticking which consumes water. + cropTile.humidity = startingHumidity; + cropTile.nutrients = startingNutrients; + cropTile.airQuality = startingAirQuality; + cropTile.nutrientStorage = startingNutrientStorage; + cropTile.waterStorage = startingWaterStorage; + // if fully grown harvest the crop + if (cropTile.getSize() >= cc.maxSize()) { + ItemStack[] stacks = cropTile.harvest_automated(false); + for (ItemStack stack : stacks) { + expected.addDrop(stack, stack.stackSize); } } - leftOverTicksFromRealRun += i - lastHarvestedAt; + cropTile.tick(); } - + // ensure it leaves the same way it came in + cropTile.humidity = startingHumidity; + cropTile.nutrients = startingNutrients; + cropTile.airQuality = startingAirQuality; + cropTile.nutrientStorage = startingNutrientStorage; + cropTile.waterStorage = startingWaterStorage; return expected; } - ItemStackMap getEIGDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG, ItemStack stack) { - ItemStackMap generated = new ItemStackMap<>(); - int imax = (30 * MINUTES) / (5 * SECONDS); - double ticks_to_ignore_per_operation = Math - .ceil((double) leftOverTicksFromRealRun / (NUMBER_OF_CROPS_PER_TEST * imax)); - for (int j = 0; j < NUMBER_OF_CROPS_PER_TEST; j++) { - GT_MetaTileEntity_ExtremeIndustrialGreenhouse.GreenHouseSlot slot = new GT_MetaTileEntity_ExtremeIndustrialGreenhouse.GreenHouseSlot( - EIG, - stack.copy(), - true, - false); - if (slot.isValid) { - for (int i = 0; i < imax; i++) { - int ticks_to_ignore = (int) Math.min(ticks_to_ignore_per_operation, leftOverTicksFromRealRun); - leftOverTicksFromRealRun -= ticks_to_ignore; - for (ItemStack ic2Drop : slot.getIC2Drops( - EIG, - (5 * SECONDS * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER)) - (double) ticks_to_ignore)) { - generated.merge(ic2Drop, ic2Drop.stackSize, Integer::sum); - } - } - } - } - + EIGDropTable getEIGDrops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG, ItemStack stack) { + EIGDropTable generated = new EIGDropTable(); + EIGIC2Bucket bucket = new EIGIC2Bucket(stack, stack.stackSize, null, false); + bucket.revalidate(EIG); + bucket.addProgress(EIG_SIMULATION_TIME, generated); return generated; } @Test void EIGDrops() { - myWorld.setBlock(10, 80, 0, Blocks.farmland, 0, 0); myWorld.setBlock(10, 81, 0, Block.getBlockFromItem(Ic2Items.crop.getItem()), 0, 0); - CropCard cc = Crops.instance.getCropCard("gregtech", "Indigo"); + // using stickreed since it has a random stage after harvest. + // it's also more preferable to test using faster growing crops since they can be harvested more often. + CropCard cc = Crops.instance.getCropCard("IC2", "stickreed"); TileEntityCrop cropTile = (TileEntityCrop) myWorld.getTileEntity(10, 81, 0); - ItemStack ccStack = cropTile.generateSeeds(cc, (byte) 10, (byte) 10, (byte) 10, (byte) 1); - for (int i = 0; i < TileEntityCrop.tickRate; i++) { - cropTile.waterStorage = 200; - cropTile.updateEntity(); - } + ItemStack ccStack = ItemCropSeed.generateItemStackFromValues(cc, (byte) 10, (byte) 10, (byte) 10, (byte) 1); GT_Item_Machines itemMachines = (GT_Item_Machines) Item.getItemFromBlock(GregTech_API.sBlockMachines); itemMachines.placeBlockAt( @@ -241,6 +225,11 @@ public class EIGTests { GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG = (GT_MetaTileEntity_ExtremeIndustrialGreenhouse) te .getMetaTileEntity(); + // update stats of crop TE to those provided by the EIG + cropTile.humidity = EIGIC2Bucket.getHumidity(EIG, false); + cropTile.nutrients = EIGIC2Bucket.getNutrients(EIG); + cropTile.airQuality = EIGIC2Bucket.getAirQuality(EIG); + int[] abc = new int[] { 0, -2, 3 }; int[] xyz = new int[] { 0, 0, 0 }; EIG.getExtendedFacing() @@ -254,89 +243,35 @@ public class EIGTests { ItemStack stackToTest = null; - for (int n = 0; n < 5; n++) { - - int[] x = new int[NUMBER_OF_TESTS_TO_DO]; - int[] y = new int[NUMBER_OF_TESTS_TO_DO]; - - // MinecraftServer.getServer() - // .addChatMessage(new ChatComponentText("[EIGTest results]")); - - for (int i = 0; i < NUMBER_OF_TESTS_TO_DO; i++) { - leftOverTicksFromRealRun = 0; - ItemStackMap expected = getRealDrops(cropTile, cc, 10, 10, 10); - ItemStackMap generated = getEIGDrops(EIG, ccStack); - - // MinecraftServer.getServer() - // .addChatMessage(new ChatComponentText("[TEST" + i + "]Real crop drops:")); - // for (Map.Entry entry : expected.entrySet()) { - // MinecraftServer.getServer() - // .addChatMessage(new ChatComponentText("- " + entry.getKey().getDisplayName() + " x" + - // entry.getValue())); - // } - - // MinecraftServer.getServer() - // .addChatMessage(new ChatComponentText("[TEST" + i + "]EIG crop drops:")); - // for (Map.Entry entry : generated.entrySet()) { - // MinecraftServer.getServer() - // .addChatMessage(new ChatComponentText("- " + entry.getKey().getDisplayName() + " x" + - // entry.getValue())); - // } - - // we are only comparing one item from drops - if (stackToTest == null) { - stackToTest = expected.entrySet() - .stream() - .max(Comparator.comparingInt(Map.Entry::getValue)) - .get() - .getKey(); - } + double realAvg = 0, eigAvg = 0; - int expectedValue = expected.getOrDefault(stackToTest, 0); - int generatedValue = generated.getOrDefault(stackToTest, 0); + for (int i = 0; i < NUMBER_OF_TESTS_TO_DO; i++) { + EIGDropTable expected = getRealDrops(cropTile, cc, 10, 10, 10); + EIGDropTable generated = getEIGDrops(EIG, ccStack); - x[i] = expectedValue; - y[i] = generatedValue; + // we are only comparing one item from drops + if (stackToTest == null) { + stackToTest = expected.entrySet() + .stream() + .max(Map.Entry.comparingByValue()) + .get() + .getKey(); } - double real_average = Arrays.stream(x) - .average() - .getAsDouble(); - double eig_average = Arrays.stream(y) - .average() - .getAsDouble(); - - double real_variance = 0d; - double a = 0d; - for (int i : x) { - a += (i - real_average) * (i - real_average); - } - a /= NUMBER_OF_TESTS_TO_DO; - real_variance = a; - - double eig_variance = 0d; - a = 0d; - for (int i : y) { - a += (i - eig_average) * (i - eig_average); - } - a /= NUMBER_OF_TESTS_TO_DO; - eig_variance = a; - - double u = (real_average - eig_average) - / Math.sqrt((real_variance / NUMBER_OF_TESTS_TO_DO) + (eig_variance / NUMBER_OF_TESTS_TO_DO)); - MinecraftServer.getServer() - .addChatMessage( - new ChatComponentText( - "real average = " + Math - .round(real_average) + " eig average = " + Math.round(eig_average) + " u = " + u)); - double test_critical_value = 1.959964d; - boolean passed = Math.abs(u) < test_critical_value; - boolean instafail = Math.abs(u) > test_critical_value * 2; - if (passed) return; - assertFalse(instafail); + realAvg += expected.getItemAmount(stackToTest); + // EIG with ic2 crops doesn't actually have variance, it uses very precise approximations that create + // accurate growth rate and drop quality approximations. + eigAvg += generated.getItemAmount(stackToTest); } - fail(); + realAvg /= NUMBER_OF_TESTS_TO_DO; + eigAvg /= NUMBER_OF_TESTS_TO_DO; + double accuracy = Math.min(realAvg / eigAvg, eigAvg / realAvg); + + String debugInfo = String.format("realAvg: %.5f | eigAvg : %.5f | accuracy = %.5f", realAvg, eigAvg, accuracy); + System.out.println(debugInfo); + // We aim for about 99% accuracy over here. + assertTrue(accuracy >= 0.99d); } } diff --git a/src/main/java/gregtech/mixin/Mixin.java b/src/main/java/gregtech/mixin/Mixin.java index 6e2b927afe..2fb3e55350 100644 --- a/src/main/java/gregtech/mixin/Mixin.java +++ b/src/main/java/gregtech/mixin/Mixin.java @@ -43,6 +43,11 @@ public enum Mixin { .setApplyIf(() -> ConfigHandler.enabledPatches[3]) .setPhase(Phase.EARLY) .setSide(Side.BOTH)), + BlockStemMixin(new Builder("Stem Crop Block Accessor").addMixinClasses("minecraft.BlockStemMixin") + .addTargetedMod(VANILLA) + .setApplyIf(() -> true) + .setPhase(Phase.EARLY) + .setSide(Side.BOTH)), // Extra utilities RemoveLastMilleniumRain(new Builder("Remove rain from the Last Millenium (Extra Utilities)") .addMixinClasses("xu.WorldProviderEndOfTimeMixin") @@ -62,7 +67,7 @@ public enum Mixin { .addTargetedMod(THAUMCRAFT) .setApplyIf(() -> ConfigHandler.enabledPatches[2]) .setPhase(Phase.LATE) - .setSide(Side.BOTH)),; + .setSide(Side.BOTH)); public static final Logger LOGGER = LogManager.getLogger("GregTech-Mixin"); diff --git a/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java b/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java new file mode 100644 index 0000000000..685e00b2a8 --- /dev/null +++ b/src/main/java/gregtech/mixin/mixins/early/minecraft/BlockStemMixin.java @@ -0,0 +1,23 @@ +package gregtech.mixin.mixins.early.minecraft; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockStem; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import kubatech.api.IBlockStemAccesor; + +@Mixin(value = BlockStem.class) +public class BlockStemMixin implements IBlockStemAccesor { + + @Shadow + @Final + private Block field_149877_a; + + @Override + public Block getCropBlock() { + return this.field_149877_a; + } +} diff --git a/src/main/java/kubatech/CommonProxy.java b/src/main/java/kubatech/CommonProxy.java index 674b87f95d..20a10b2a52 100644 --- a/src/main/java/kubatech/CommonProxy.java +++ b/src/main/java/kubatech/CommonProxy.java @@ -39,6 +39,7 @@ import cpw.mods.fml.common.event.FMLServerStoppedEvent; import cpw.mods.fml.common.event.FMLServerStoppingEvent; import kubatech.commands.CommandHandler; import kubatech.config.Config; +import kubatech.loaders.EIGBucketLoader; import kubatech.loaders.MTLoader; import kubatech.loaders.MobHandlerLoader; import kubatech.loaders.RecipeLoader; @@ -67,6 +68,7 @@ public class CommonProxy { public void postInit(FMLPostInitializationEvent event) { RecipeLoader.addRecipes(); + EIGBucketLoader.LoadEIGBuckets(); if (Thaumcraft.isModLoaded()) TCLoader.init(); } diff --git a/src/main/java/kubatech/api/EIGDynamicInventory.java b/src/main/java/kubatech/api/EIGDynamicInventory.java new file mode 100644 index 0000000000..1c703fe2fa --- /dev/null +++ b/src/main/java/kubatech/api/EIGDynamicInventory.java @@ -0,0 +1,510 @@ +package kubatech.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; + +import org.lwjgl.opengl.GL11; + +import com.gtnewhorizons.modularui.api.GlStateManager; +import com.gtnewhorizons.modularui.api.ModularUITextures; +import com.gtnewhorizons.modularui.api.drawable.IDrawable; +import com.gtnewhorizons.modularui.api.drawable.ItemDrawable; +import com.gtnewhorizons.modularui.api.drawable.UITexture; +import com.gtnewhorizons.modularui.api.math.Alignment; +import com.gtnewhorizons.modularui.api.math.Color; +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; +import com.gtnewhorizons.modularui.api.widget.Widget; +import com.gtnewhorizons.modularui.common.internal.Theme; +import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui; +import com.gtnewhorizons.modularui.common.widget.ButtonWidget; +import com.gtnewhorizons.modularui.common.widget.ChangeableWidget; +import com.gtnewhorizons.modularui.common.widget.DynamicPositionedRow; +import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; +import com.gtnewhorizons.modularui.common.widget.Scrollable; +import com.kuba6000.mobsinfo.api.utils.ItemID; + +import kubatech.api.gui.AutoScalingStackSizeText; +import kubatech.api.helpers.GTHelper; +import kubatech.api.utils.ModUtils; + +public class EIGDynamicInventory { + + int width, height; + Supplier maxSeedCountGetter; + Supplier maxSeedTypeGetter; + Supplier usedSeedCountGetter; + Supplier usedSeedTypesGetter; + private int maxSeedTypes = 0; + private int maxSeedCount = 0; + private int usedSeedTypes = 0; + private int usedSeedCount = 0; + List inventory; + TInventoryGetter inventoryGetter; + TInventoryInjector inventoryInjector = null; + TInventoryExtractor inventoryExtractor = null; + TInventoryReplacerOrMerger inventoryReplacer = null; + Supplier isEnabledGetter = null; + boolean isEnabled = true; + + public EIGDynamicInventory(int width, int height, Supplier maxSeedTypeGetter, + Supplier maxSeedCountGetter, Supplier usedSeedTypesGetter, + Supplier usedSeedCountGetter, List inventory, TInventoryGetter inventoryGetter) { + this.width = width; + this.height = height; + this.maxSeedTypeGetter = maxSeedTypeGetter; + this.maxSeedCountGetter = maxSeedCountGetter; + this.usedSeedTypesGetter = usedSeedTypesGetter; + this.usedSeedCountGetter = usedSeedCountGetter; + this.inventory = inventory; + this.inventoryGetter = inventoryGetter; + } + + public EIGDynamicInventory allowInventoryInjection(TInventoryInjector inventoryInjector) { + this.inventoryInjector = inventoryInjector; + return this; + } + + public EIGDynamicInventory allowInventoryExtraction(TInventoryExtractor inventoryExtractor) { + this.inventoryExtractor = inventoryExtractor; + return this; + } + + public EIGDynamicInventory allowInventoryReplace(TInventoryReplacerOrMerger inventoryReplacer) { + this.inventoryReplacer = inventoryReplacer; + return this; + } + + public EIGDynamicInventory setEnabled(Supplier isEnabled) { + this.isEnabledGetter = isEnabled; + return this; + } + + public UITexture getItemSlot() { + return ModularUITextures.ITEM_SLOT; + } + + @SuppressWarnings("UnstableApiUsage") + public Widget asWidget(ModularWindow.Builder builder, UIBuildContext buildContext) { + ChangeableWidget container = new ChangeableWidget(() -> createWidget(buildContext.getPlayer())); + // TODO: Only reset the widget when there are more slot stacks, otherwise just refresh them somehow + + container + // max seed types + .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> { + int i = this.maxSeedTypeGetter.get(); + if (this.maxSeedTypes != i) { + this.maxSeedTypes = i; + container.notifyChangeNoSync(); + } + return i; + }, i -> { + if (this.maxSeedTypes != i) { + this.maxSeedTypes = i; + container.notifyChangeNoSync(); + } + }), builder) + // used seed types + .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> { + int i = this.usedSeedTypesGetter.get(); + if (this.usedSeedTypes != i) { + this.usedSeedTypes = i; + container.notifyChangeNoSync(); + } + return i; + }, i -> { + if (this.usedSeedTypes != i) { + this.usedSeedTypes = i; + container.notifyChangeNoSync(); + } + }), builder) + // max seed count + .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> { + int i = this.maxSeedCountGetter.get(); + if (this.maxSeedCount != i) { + this.maxSeedCount = i; + container.notifyChangeNoSync(); + } + return i; + }, i -> { + if (this.maxSeedCount != i) { + this.maxSeedCount = i; + container.notifyChangeNoSync(); + } + }), builder) + // used seed count + .attachSyncer(new FakeSyncWidget.IntegerSyncer(() -> { + int i = this.usedSeedCountGetter.get(); + if (this.usedSeedCount != i) { + this.usedSeedCount = i; + container.notifyChangeNoSync(); + } + return i; + }, i -> { + if (this.usedSeedCount != i) { + this.usedSeedCount = i; + container.notifyChangeNoSync(); + } + }), builder) + + .attachSyncer(new FakeSyncWidget.ListSyncer<>(() -> { + List newDrawables = new ArrayList<>(); + for (int i = 0, mStorageSize = inventory.size(); i < mStorageSize; i++) { + T slot = inventory.get(i); + if (slot == null) { + continue; + } + ItemStack stack = inventoryGetter.get(slot); + newDrawables + .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList<>(Collections.singletonList(i)))); + } + if (!Objects.equals(newDrawables, drawables)) { + drawables = newDrawables; + container.notifyChangeNoSync(); + } + return drawables; + }, l -> { + drawables.clear(); + drawables.addAll(l); + container.notifyChangeNoSync(); + }, (buffer, i) -> { + try { + i.write(buffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, buffer -> { + try { + return GTHelper.StackableItemSlot.read(buffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + }), builder); + if (isEnabledGetter != null) { + container.attachSyncer(new FakeSyncWidget.BooleanSyncer(isEnabledGetter, i -> isEnabled = i), builder); + } + return container; + } + + List drawables = new ArrayList<>(); + + private Widget createWidget(EntityPlayer player) { + Scrollable dynamicInventoryWidget = new Scrollable().setVerticalScroll(); + + ArrayList buttons = new ArrayList<>(); + + if (!ModUtils.isClientThreaded()) { + HashMap itemMap = new HashMap<>(); + HashMap stackMap = new HashMap<>(); + HashMap> realSlotMap = new HashMap<>(); + drawables = new ArrayList<>(); + for (int i = 0, inventorySize = inventory.size(); i < inventorySize; i++) { + T slot = inventory.get(i); + if (slot == null) { + continue; + } + ItemStack stack = inventoryGetter.get(slot); + drawables + .add(new GTHelper.StackableItemSlot(1, stack, new ArrayList(Collections.singleton(i)))); + } + } + + for (int ID = 0; ID < drawables.size(); ID++) { + final int finalID = ID; + + buttons.add(new ButtonWidget() { + + @Override + public void drawBackground(float partialTicks) { + super.drawBackground(partialTicks); + if (!isEnabled) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GlStateManager.colorMask(true, true, true, false); + ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80)); + GlStateManager.colorMask(true, true, true, true); + GL11.glDisable(GL11.GL_BLEND); + } + // Copied from SlotWidget#draw + else if (isHovering() && !getContext().getCursor() + .hasDraggable()) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GlStateManager.colorMask(true, true, true, false); + ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight()); + GlStateManager.colorMask(true, true, true, true); + GL11.glDisable(GL11.GL_BLEND); + } + } + }.setPlayClickSound(false) + .setOnClick((clickData, widget) -> { + if (!(player instanceof EntityPlayerMP)) return; + if (!isEnabledGetter.get()) return; + + if (clickData.mouseButton == 2) { + // special button handler goes here + if (drawables.size() <= finalID) return; + if (player.capabilities.isCreativeMode && player.inventory.getItemStack() == null) { + int realID = drawables.get(finalID).realSlots.get(0); + ItemStack stack = inventoryGetter.get(inventory.get(realID)) + .copy(); + stack.stackSize = stack.getMaxStackSize(); + player.inventory.setItemStack(stack); + ((EntityPlayerMP) player).isChangingQuantityOnly = false; + ((EntityPlayerMP) player).updateHeldItem(); + return; + } + } else if (clickData.shift) { + if (inventoryExtractor == null) return; + if (drawables.size() <= finalID) return; + int realID = drawables.get(finalID).realSlots.get(0); + T toRemoveFrom = this.inventory.get(realID); + ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player); + if (removed != null) { + if (player.inventory.addItemStackToInventory(removed)) + player.inventoryContainer.detectAndSendChanges(); + else player.entityDropItem(removed, 0.f); + return; + } + } else { + ItemStack input = player.inventory.getItemStack(); + if (input != null) { + if (inventoryInjector == null) return; + if (clickData.mouseButton == 1) { + ItemStack copy = input.copy(); + copy.stackSize = 1; + inventoryInjector.inject(copy); + if (copy.stackSize == 1) return; + input.stackSize--; + if (input.stackSize > 0) { + // clearing and updating the held item value like this is the only + // way i found to be able to reliably update the item count in the UI. + player.inventory.setItemStack(null); + ((EntityPlayerMP) player).updateHeldItem(); + player.inventory.setItemStack(input); + ((EntityPlayerMP) player).updateHeldItem(); + return; + } else player.inventory.setItemStack(null); + } else { + inventoryInjector.inject(input); + if (input.stackSize > 0) { + // clearing and updating the held item value like this is the only + // way i found to be able to reliably update the item count in the UI. + player.inventory.setItemStack(null); + ((EntityPlayerMP) player).updateHeldItem(); + player.inventory.setItemStack(input); + ((EntityPlayerMP) player).updateHeldItem(); + return; + } else player.inventory.setItemStack(null); + } + ((EntityPlayerMP) player).isChangingQuantityOnly = false; + ((EntityPlayerMP) player).updateHeldItem(); + return; + } + if (drawables.size() > finalID) { + if (inventoryExtractor == null) return; + int realID = drawables.get(finalID).realSlots.get(0); + T toRemoveFrom = this.inventory.get(realID); + ItemStack removed = this.inventoryExtractor.extract(toRemoveFrom, (EntityPlayerMP) player); + if (removed != null) { + player.inventory.setItemStack(removed); + ((EntityPlayerMP) player).isChangingQuantityOnly = false; + ((EntityPlayerMP) player).updateHeldItem(); + return; + } + } + } + }) + .setBackground(() -> { + ItemStack stack = drawables.get(finalID).stack; + float slotSize = 16.0f; + IDrawable itemDrawable = new ItemDrawable(stack).withFixedSize(slotSize, slotSize, 1, 1); + IDrawable stackSizeText = new AutoScalingStackSizeText(stack.stackSize).color(Color.WHITE.normal) + .shadow() + .alignment(Alignment.BottomRight) + .measure(); + + return new IDrawable[] { getItemSlot(), itemDrawable, stackSizeText }; + }) + .dynamicTooltip(() -> { + if (drawables.size() > finalID) { + ItemStack stack = drawables.get(finalID).stack; + List tip = new LinkedList<>(); + for (Object o : stack.getTooltip(player, false)) { + tip.add(o.toString()); + } + if (tip.size() >= 1 && tip.get(0) != null) { + tip.set(0, stack.stackSize + " x " + tip.get(0)); + } + return tip; + } + return Collections.emptyList(); + }) + .setSize(18, 18)); + } + + // only add the extra slot if we are still able to insert + if (this.usedSeedCount < this.maxSeedCount) { + buttons.add(new ButtonWidget() { + + @Override + public void drawBackground(float partialTicks) { + super.drawBackground(partialTicks); + if (!isEnabled) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GlStateManager.colorMask(true, true, true, false); + ModularGui.drawSolidRect(1, 1, 16, 16, Color.withAlpha(Color.BLACK.normal, 0x80)); + GlStateManager.colorMask(true, true, true, true); + GL11.glDisable(GL11.GL_BLEND); + } + // Copied from SlotWidget#draw + else if (isHovering() && !getContext().getCursor() + .hasDraggable()) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GlStateManager.colorMask(true, true, true, false); + ModularGui.drawSolidRect(1, 1, 16, 16, Theme.INSTANCE.getSlotHighlight()); + GlStateManager.colorMask(true, true, true, true); + GL11.glDisable(GL11.GL_BLEND); + } + } + }.setPlayClickSound(false) + .setOnClick((clickData, widget) -> { + if (!(player instanceof EntityPlayerMP)) return; + if (!isEnabledGetter.get()) return; + ItemStack input = player.inventory.getItemStack(); + if (input != null) { + if (clickData.mouseButton == 1) { + ItemStack copy = input.copy(); + copy.stackSize = 1; + inventoryInjector.inject(copy); + if (copy.stackSize == 1) return; + + input.stackSize--; + if (input.stackSize > 0) { + // clearing and updating the held item value like this is the only + // way i found to be able to reliably update the item count in the UI. + player.inventory.setItemStack(null); + ((EntityPlayerMP) player).updateHeldItem(); + player.inventory.setItemStack(input); + ((EntityPlayerMP) player).updateHeldItem(); + return; + } else player.inventory.setItemStack(null); + } else { + inventoryInjector.inject(input); + if (input.stackSize > 0) { + // clearing and updating the held item value like this is the only + // way i found to be able to reliably update the item count in the UI. + player.inventory.setItemStack(null); + ((EntityPlayerMP) player).updateHeldItem(); + player.inventory.setItemStack(input); + ((EntityPlayerMP) player).updateHeldItem(); + return; + } else player.inventory.setItemStack(null); + } + ((EntityPlayerMP) player).isChangingQuantityOnly = false; + ((EntityPlayerMP) player).updateHeldItem(); + return; + } + }) + .setBackground(() -> { + IDrawable itemSlot = getItemSlot(); + + IDrawable stackSizeText = new AutoScalingStackSizeText(this.maxSeedCount - this.usedSeedCount) + .color(Color.WHITE.normal) + .shadow() + .alignment(Alignment.BottomRight) + .measure(); + + return new IDrawable[] { itemSlot, stackSizeText }; + }) + .dynamicTooltip(() -> { + // TODO: all l10n for insertion slot tooltip. + List tip = new ArrayList<>(); + tip.add( + EnumChatFormatting.DARK_PURPLE + "Remaining seed types: " + + (this.maxSeedTypes - this.usedSeedTypes)); + tip.add( + EnumChatFormatting.DARK_GREEN + "Remaining seed capacity: " + + (this.maxSeedCount - this.usedSeedCount)); + return tip; + }) + .setSize(18, 18)); + } + + final int perRow = width / 18; + for (int i = 0, imax = ((buttons.size() - 1) / perRow); i <= imax; i++) { + DynamicPositionedRow row = new DynamicPositionedRow().setSynced(false); + for (int j = 0, jmax = (i == imax ? (buttons.size() - 1) % perRow : (perRow - 1)); j <= jmax; j++) { + final int finalI = i * perRow; + final int finalJ = j; + final int ID = finalI + finalJ; + row.widget(buttons.get(ID)); + } + dynamicInventoryWidget.widget(row.setPos(0, i * 18)); + } + dynamicInventoryWidget.setSize(width, height); + return dynamicInventoryWidget; + } + + @FunctionalInterface + public interface TInventoryGetter { + + /** + * Allows to get an ItemStack from the dynamic inventory + * + * @param from Dynamic inventory item from which we want to take an item out + * @return ItemStack or null if inaccessible + */ + ItemStack get(T from); + } + + @FunctionalInterface + public interface TInventoryInjector { + + /** + * Allows to insert an item to the dynamic inventory + * + * @param what ItemStack which we are trying to insert + * @return Leftover ItemStack (stackSize == 0 if everything has been inserted) or null + */ + ItemStack inject(ItemStack what); + } + + @FunctionalInterface + public interface TInventoryExtractor { + + /** + * Allows to extract an item from the dynamic inventory + * + * @return Item that we took out or null + */ + ItemStack extract(T container, EntityPlayerMP player); + } + + @FunctionalInterface + public interface TInventoryReplacerOrMerger { + + /** + * Allows to replace an item in Dynamic Inventory + * + * @param where which index we want to replace + * @param stack what stack we want to replace it with + * @return Stack that we are left with or null + */ + ItemStack replaceOrMerge(int where, ItemStack stack); + } + +} diff --git a/src/main/java/kubatech/api/IBlockStemAccesor.java b/src/main/java/kubatech/api/IBlockStemAccesor.java new file mode 100644 index 0000000000..8f2c37e15f --- /dev/null +++ b/src/main/java/kubatech/api/IBlockStemAccesor.java @@ -0,0 +1,8 @@ +package kubatech.api; + +import net.minecraft.block.Block; + +public interface IBlockStemAccesor { + + Block getCropBlock(); +} diff --git a/src/main/java/kubatech/api/eig/EIGBucket.java b/src/main/java/kubatech/api/eig/EIGBucket.java new file mode 100644 index 0000000000..6a3dbdb642 --- /dev/null +++ b/src/main/java/kubatech/api/eig/EIGBucket.java @@ -0,0 +1,247 @@ +package kubatech.api.eig; + +import static kubatech.api.utils.ItemUtils.readItemStackFromNBT; +import static kubatech.api.utils.ItemUtils.writeItemStackToNBT; + +import java.util.LinkedList; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.EnumChatFormatting; + +import gregtech.api.util.GT_Utility; +import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse; + +public abstract class EIGBucket { + + protected ItemStack seed; + protected int seedCount; + protected ItemStack[] supportItems; + + public EIGBucket(ItemStack seed, int seedCount, ItemStack[] supportItem) { + this.seed = seed.copy(); + this.seed.stackSize = 1; + this.seedCount = seedCount; + this.supportItems = supportItem; + } + + public EIGBucket(NBTTagCompound nbt) { + this.seed = readItemStackFromNBT(nbt.getCompoundTag("seed")); + this.seedCount = nbt.getInteger("count"); + + // parse support items + if (nbt.hasKey("supportItems", 9)) { + NBTTagList supportItemsNBTList = nbt.getTagList("supportItems", 10); + if (supportItemsNBTList.tagCount() > 0) { + this.supportItems = new ItemStack[supportItemsNBTList.tagCount()]; + for (int i = 0; i < supportItemsNBTList.tagCount(); i++) { + this.supportItems[i] = readItemStackFromNBT(supportItemsNBTList.getCompoundTagAt(i)); + } + } else { + supportItems = null; + } + } else { + supportItems = null; + } + } + + /** + * Creates a persistent save of the bucket's current data. + * + * @return The nbt data for this bucket. + */ + public NBTTagCompound save() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setString("type", this.getNBTIdentifier()); + nbt.setTag("seed", writeItemStackToNBT(this.seed)); + nbt.setInteger("count", this.seedCount); + if (this.supportItems != null && this.supportItems.length > 0) { + NBTTagList supportItemNBT = new NBTTagList(); + for (ItemStack supportItem : this.supportItems) { + supportItemNBT.appendTag(writeItemStackToNBT(supportItem)); + } + nbt.setTag("supportItems", supportItemNBT); + } + return nbt; + } + + /** + * Gets an item stack representing the seeds in this bucket + * + * @return an item stack representing the seeds in this bucket. + */ + public ItemStack getSeedStack() { + ItemStack copied = this.seed.copy(); + copied.stackSize = this.seedCount; + return copied; + } + + /** + * Gets the number of seeds in this bucket + * + * @return gets the number of seeds in this bucket. + */ + public int getSeedCount() { + return this.seedCount; + } + + /** + * Gets the display name of the seed in this bucket + * + * @return The display name of the seed. + */ + public String getDisplayName() { + return this.seed.getDisplayName(); + } + + public String getInfoData() { + StringBuilder sb = new StringBuilder(); + // display invalid buckets, we don't want people to think they lost their seeds or something. + sb.append(this.isValid() ? EnumChatFormatting.GREEN : EnumChatFormatting.RED); + sb.append("x"); + sb.append(this.getSeedCount()); + sb.append(" "); + sb.append(this.getDisplayName()); + this.getAdditionalInfoData(sb); + sb.append(EnumChatFormatting.RESET); + return sb.toString(); + } + + protected void getAdditionalInfoData(StringBuilder sb) {} + + /** + * Attempts to add seeds to tbe bucket if the input is compatible + * + * @param input A stack of an item that may be able to be added to our current bucket. + * @param maxConsume The maximum amount of seeds to add to this bucket. + * @param simulate True if you want to see if you can add more seeds (useful for support item checks) + * @return number of seeds consumed, 0 for wrong item, -1 if it missed the support items, -2 if you tried to consume + * 0 or less items; + */ + public int tryAddSeed(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse, ItemStack input, int maxConsume, + boolean simulate) { + // Abort is input if empty + if (input == null || input.stackSize <= 0) return -2; + // Cap max to input count + maxConsume = Math.min(maxConsume, input.stackSize); + // Abort if item isn't an identical seed. + if (!GT_Utility.areStacksEqual(this.seed, input, false)) return 0; + + // no support items, consume and exit early. + if (this.supportItems == null || this.supportItems.length <= 0) { + if (!simulate) { + input.stackSize -= maxConsume; + this.seedCount += maxConsume; + } + return maxConsume; + } + + // Check if the item is found + LinkedList toConsumeFrom = new LinkedList<>(); + supportLoop: for (ItemStack supportItem : this.supportItems) { + for (ItemStack otherInput : greenhouse.getStoredInputs()) { + // filter usable inputs + if (otherInput == null || otherInput.stackSize <= 0) continue; + if (!GT_Utility.areStacksEqual(supportItem, otherInput, false)) continue; + // update max consume again + maxConsume = Math.min(maxConsume, otherInput.stackSize); + toConsumeFrom.addLast(otherInput); + continue supportLoop; + } + // no support found, no seeds added + return -1; + } + + // consume items + if (!simulate) { + input.stackSize -= maxConsume; + for (ItemStack stack : toConsumeFrom) { + stack.stackSize -= maxConsume; + } + this.seedCount += maxConsume; + } + return maxConsume; + } + + /** + * Attempts to remove a seed from the bucket + * + * @param toRemove The maximum amount of items to remove. + * @return The items that were removed from the bucket. Null if the bucket is empty. + */ + public ItemStack[] tryRemoveSeed(int toRemove, boolean simulate) { + // validate inputs + toRemove = Math.min(this.seedCount, toRemove); + if (toRemove <= 0) return null; + + // consume and return output + ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)]; + ret[0] = this.seed.copy(); + ret[0].stackSize = toRemove; + if (this.supportItems != null) { + for (int i = 0; i < this.supportItems.length; i++) { + ret[i + 1] = this.supportItems[i].copy(); + ret[i + 1].stackSize = toRemove; + } + } + if (!simulate) { + this.seedCount -= toRemove; + } + return ret; + } + + /** + * Sets the seed count to 0 and returns item stacks representing every item in this bucket. + * + * @return The contents of the bucket + */ + public ItemStack[] emptyBucket() { + if (this.seedCount <= 0) return null; + ItemStack[] ret = new ItemStack[1 + (this.supportItems == null ? 0 : this.supportItems.length)]; + ret[0] = this.seed.copy(); + ret[0].stackSize = this.seedCount; + if (this.supportItems != null) { + for (int i = 0; i < this.supportItems.length; i++) { + ret[i + 1] = this.supportItems[i].copy(); + ret[i + 1].stackSize = this.seedCount; + } + } + this.seedCount = 0; + return ret; + } + + /** + * Returns true if the bucket can output items. + * + * @return true if the bucket is valid. + */ + public boolean isValid() { + return this.seed != null && this.seedCount > 0; + } + + /** + * Gets the identifier used to identify this class during reconstruction + * + * @return the identifier for this bucket type. + */ + protected abstract String getNBTIdentifier(); + + /** + * Adds item drops to the item tracker. + * + * @param multiplier A multiplier to apply to the output. + * @param tracker The item drop tracker + */ + public abstract void addProgress(double multiplier, EIGDropTable tracker); + + /** + * Attempts to revalidate a seed bucket. If it returns false, attempt to seed and support items and delete the + * bucket. + * + * @param greenhouse The greenhouse that contains the bucket. + * @return True if the bucket was successfully validated. {@link EIGBucket#isValid()} should also return true. + */ + public abstract boolean revalidate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse greenhouse); + +} diff --git a/src/main/java/kubatech/api/eig/EIGDropTable.java b/src/main/java/kubatech/api/eig/EIGDropTable.java new file mode 100644 index 0000000000..bb5bbe6456 --- /dev/null +++ b/src/main/java/kubatech/api/eig/EIGDropTable.java @@ -0,0 +1,224 @@ +package kubatech.api.eig; + +import static kubatech.api.utils.ItemUtils.readItemStackFromNBT; +import static kubatech.api.utils.ItemUtils.writeItemStackToNBT; + +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap; + +public class EIGDropTable { + + private static final String NBT_DROP_TABLE_ITEM_KEY = "item"; + private static final String NBT_DROP_TABLE_COUNT_KEY = "count"; + + private final ItemStackMap dropTable; + + /** + * Initialises a new empty drop table. + */ + public EIGDropTable() { + this.dropTable = new ItemStackMap<>(true); + } + + /** + * Loads a serialised drop table from nbt. + * + * @param nbt The nbt tag that contains the key for a drop table + * @param key The name of the key name for the drop table. + */ + public EIGDropTable(NBTTagCompound nbt, String key) { + // should create an empty table if no drops are found. + this(nbt.getTagList(key, 10)); + } + + /** + * Loads a serialised drop table from nbt. + * + * @param nbt The nbt tag that contains the key for a drop table + */ + public EIGDropTable(NBTTagList nbt) { + this(); + for (int i = 0; i < nbt.tagCount(); i++) { + NBTTagCompound drop = nbt.getCompoundTagAt(i); + dropTable.merge( + readItemStackFromNBT(drop.getCompoundTag(NBT_DROP_TABLE_ITEM_KEY)), + drop.getDouble(NBT_DROP_TABLE_COUNT_KEY), + Double::sum); + } + } + + /** + * Serialises the drop table to nbt + * + * @return The serialised drop table. + */ + public NBTTagList save() { + NBTTagList nbt = new NBTTagList(); + for (Map.Entry entry : this.dropTable.entrySet()) { + NBTTagCompound entryNBT = new NBTTagCompound(); + entryNBT.setTag(NBT_DROP_TABLE_ITEM_KEY, writeItemStackToNBT(entry.getKey())); + entryNBT.setDouble(NBT_DROP_TABLE_COUNT_KEY, entry.getValue()); + nbt.appendTag(entryNBT); + } + return nbt; + } + + /** + * Adds a drop to the drop table + * + * @param itemStack The item to add to the table. + * @param amount The amount to add to the table. + */ + public void addDrop(ItemStack itemStack, double amount) { + ItemStack key = itemStack.copy(); + key.stackSize = 1; + this.dropTable.merge(key, amount, Double::sum); + } + + /** + * Adds the values from this drop table to another, but multiplies the amount by a random amount bound by variance. + * + * @param target The drop table that you want to add the value to. + * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz + * @param rand The random source for the variance. + */ + public void addTo(EIGDropTable target, double variance, Random rand) { + this.addTo(target, 1.0, variance, rand); + } + + /** + * Adds the values from this drop table to another, but multiplies the amount by a multiplier and a random amount + * bound by variance. + * + * @param target The drop table that you want to add the value to. + * @param multiplier A multiplier to apply to all amounts from this drop table. + * @param variance How much to vary the amounts of this drop table to, 0 < x < 1 plz. + * @param rand The random source for the variance. + */ + public void addTo(EIGDropTable target, double multiplier, double variance, Random rand) { + this.addTo(target, variance * (rand.nextDouble() - 0.5) * multiplier); + } + + /** + * Adds the values from this drop table to another. + * + * @param target The drop table that you want to add the value to. + */ + public void addTo(EIGDropTable target) { + this.addTo(target, 1.0); + } + + /** + * Adds the values from this drop table to another but multiplies the values by a multiplier. + * + * @param target The drop table that you want to add the value to. + * @param multiplier A multiplier to apply to all amounts from this drop table. + */ + public void addTo(EIGDropTable target, double multiplier) { + for (Map.Entry entry : this.dropTable.entrySet()) { + target.dropTable.merge(entry.getKey(), entry.getValue() * multiplier, Double::sum); + } + } + + /** + * Checks if the drop table is empty; + * + * @return true if empty. + */ + public boolean isEmpty() { + return this.dropTable.isEmpty(); + } + + /** + * Returns the entry set for this drop table. + * + * @return ItemStack -> amount + */ + public Set> entrySet() { + return this.dropTable.entrySet(); + } + + /** + * Gets the amount for a specific item. + * + * @param item The item to look for. + * @return 0 if nothing is found else a positive value. + */ + public double getItemAmount(ItemStack item) { + if (this.dropTable.containsKey(item)) { + return this.dropTable.get(item); + } + return 0; + } + + /** + * Sets the amount for a specific item. + * + * @param item The item to look for. + */ + public void setItemAmount(ItemStack item, double value) { + this.dropTable.put(item, value); + } + + /** + * Removes an item from the drop table + * + * @param item The item to remove from the drop table. + */ + public void removeItem(ItemStack item) { + this.dropTable.remove(item); + } + + /** + * Creates a new drop table that is the intersection of this drop table and another. + * + * + * @param with The drop table to intersect with. + * @return The result of the intersection. + */ + public EIGDropTable intersect(EIGDropTable with) { + EIGDropTable ret = new EIGDropTable(); + for (ItemStack key : with.dropTable.keySet()) { + if (this.dropTable.containsKey(key)) { + ret.addDrop(key, this.dropTable.get(key)); + } + } + return ret; + } + + /** + * Consumes drops with drop counts above 1 and returns a list of the consumed item stacks. + * + * @return The list of consumed items; + */ + public ItemStack[] getDrops() { + // doesn't need to filter for less than 0 so that the EIG displays the progress of incomplete items. + return this.dropTable.entrySet() + .parallelStream() + .map(EIGDropTable::computeDrops) + .toArray(ItemStack[]::new); + } + + /** + * Consumes the items in the entry and returns the consumed item without removing partial items. + * + * @param entry The entry to consume from + * @return The item tha twas removed. + */ + private static ItemStack computeDrops(Map.Entry entry) { + ItemStack copied = entry.getKey() + .copy(); + copied.stackSize = (int) Math.floor(entry.getValue()); + if (entry.getValue() >= 1.0d) { + entry.setValue(entry.getValue() % 1); + } + return copied; + } +} diff --git a/src/main/java/kubatech/api/eig/EIGMode.java b/src/main/java/kubatech/api/eig/EIGMode.java new file mode 100644 index 0000000000..68ad633773 --- /dev/null +++ b/src/main/java/kubatech/api/eig/EIGMode.java @@ -0,0 +1,154 @@ +package kubatech.api.eig; + +import static kubatech.kubatech.error; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse; + +public abstract class EIGMode { + + public abstract int getUIIndex(); + + public abstract String getName(); + + public abstract int getMinVoltageTier(); + + publ