diff --git a/addon.gradle b/addon.gradle index 49206f7f..a774badd 100644 --- a/addon.gradle +++ b/addon.gradle @@ -3,3 +3,44 @@ tasks.named("jar", Jar).configure { attributes("Main-class": "kubatech.standalone") } } + + +SourceSet functionalTestSet = null + +sourceSets { + functionalTestSet = create("functionalTest") { + java { + srcDir("src/functionalTest/java") + compileClasspath += sourceSets.patchedMc.output + sourceSets.main.output + } + } +} + +configurations { configs -> + // Keep all dependencies from the main mod in the functional test mod + named(functionalTestSet.compileClasspathConfigurationName).configure {it.extendsFrom(named("compileClasspath").get())} + named(functionalTestSet.runtimeClasspathConfigurationName).configure {it.extendsFrom(named("runtimeClasspath").get())} + named(functionalTestSet.annotationProcessorConfigurationName).configure {it.extendsFrom(named("annotationProcessor").get())} +} + +tasks.register(functionalTestSet.jarTaskName, Jar) { + from(functionalTestSet.output) + archiveClassifier.set("functionalTests") + // we don't care about the version number here, keep it stable to avoid polluting the tmp directory + archiveVersion.set("1.0") + destinationDirectory.set(new File(buildDir, "tmp")) +} +tasks.named("assemble").configure { + dependsOn(functionalTestSet.jarTaskName) +} + +// Run tests in the default runServer/runClient configurations +tasks.named("runServer", JavaExec).configure { + dependsOn(functionalTestSet.jarTaskName) + classpath(configurations.named(functionalTestSet.runtimeClasspathConfigurationName), tasks.named(functionalTestSet.jarTaskName)) +} + +tasks.named("runClient", JavaExec).configure { + dependsOn(functionalTestSet.jarTaskName) + classpath(configurations.named(functionalTestSet.runtimeClasspathConfigurationName), tasks.named(functionalTestSet.jarTaskName)) +} diff --git a/dependencies.gradle b/dependencies.gradle index c2ae58c6..b7ee4687 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -100,4 +100,11 @@ dependencies { //runtimeOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev") //runtimeOnly("com.github.GTNewHorizons:BloodMagic:1.5.1:dev") //api("curse.maven:witchery-69673:2234410") + + testImplementation('junit:junit:4.12') + functionalTestImplementation(platform('org.junit:junit-bom:5.9.2')) + functionalTestImplementation('org.junit.jupiter:junit-jupiter') + functionalTestImplementation('org.junit.platform:junit-platform-engine') + functionalTestImplementation('org.junit.platform:junit-platform-launcher') + functionalTestImplementation('org.junit.platform:junit-platform-reporting') } diff --git a/settings.gradle b/settings.gradle index 16a5b4ca..c52d518d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,7 +17,7 @@ pluginManagement { } plugins { - id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.14' + id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.19' } diff --git a/src/functionalTest/java/kubatech/test/EIGTests.java b/src/functionalTest/java/kubatech/test/EIGTests.java new file mode 100644 index 00000000..1bb88814 --- /dev/null +++ b/src/functionalTest/java/kubatech/test/EIGTests.java @@ -0,0 +1,342 @@ +/* + * spotless:off + * KubaTech - Gregtech Addon + * Copyright (C) 2022 - 2024 kuba6000 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * spotless:on + */ + +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 java.io.File; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +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; +import net.minecraft.world.WorldProviderSurface; +import net.minecraft.world.WorldServer; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.storage.IChunkLoader; +import net.minecraft.world.storage.IPlayerFileData; +import net.minecraft.world.storage.ISaveHandler; +import net.minecraft.world.storage.WorldInfo; +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; +import ic2.api.crops.CropCard; +import ic2.api.crops.Crops; +import ic2.core.Ic2Items; +import ic2.core.crop.TileEntityCrop; +import kubatech.tileentity.gregtech.multiblock.GT_MetaTileEntity_ExtremeIndustrialGreenhouse; + +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; + + static World myWorld; + + public EIGTests() { + if (!DimensionManager.isDimensionRegistered(256)) { + DimensionManager.registerProviderType(256, WorldProviderSurface.class, false); + DimensionManager.registerDimension(256, 256); + } + if (myWorld == null) { + myWorld = new WorldServer(MinecraftServer.getServer(), new ISaveHandler() { + + @Override + public WorldInfo loadWorldInfo() { + return null; + } + + @Override + public void checkSessionLock() throws MinecraftException { + + } + + @Override + public IChunkLoader getChunkLoader(WorldProvider p_75763_1_) { + return null; + } + + @Override + public void saveWorldInfoWithPlayer(WorldInfo p_75755_1_, NBTTagCompound p_75755_2_) { + + } + + @Override + public void saveWorldInfo(WorldInfo p_75761_1_) { + + } + + @Override + public IPlayerFileData getSaveHandler() { + return null; + } + + @Override + public void flush() { + + } + + @Override + public File getWorldDirectory() { + return null; + } + + @Override + public File getMapFileFromName(String p_75758_1_) { + return null; + } + + @Override + public String getWorldDirectoryName() { + return "dummy"; + } + }, + "DummyTestWorld", + 256, + new WorldSettings(256, WorldSettings.GameType.SURVIVAL, false, false, WorldType.DEFAULT), + MinecraftServer.getServer().theProfiler) { + + @Override + public File getChunkSaveLocation() { + return new File("ignoreme"); + } + + @Override + public int getBlockLightValue(int p_72957_1_, int p_72957_2_, int p_72957_3_) { + return 4; + } + }; + } + } + + private static int leftOverTicksFromRealRun = 0; + + ItemStackMap 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); + } + } + } + leftOverTicksFromRealRun += i - lastHarvestedAt; + } + + 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); + } + } + } + } + + 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"); + 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(); + } + + GT_Item_Machines itemMachines = (GT_Item_Machines) Item.getItemFromBlock(GregTech_API.sBlockMachines); + itemMachines.placeBlockAt( + new ItemStack(itemMachines, 1, EIG_CONTROLLER_METADATA), + null, + myWorld, + 0, + 81, + 0, + 2, + 0, + 0, + 0, + EIG_CONTROLLER_METADATA); + IGregTechTileEntity te = (IGregTechTileEntity) myWorld.getTileEntity(0, 81, 0); + GT_MetaTileEntity_ExtremeIndustrialGreenhouse EIG = (GT_MetaTileEntity_ExtremeIndustrialGreenhouse) te + .getMetaTileEntity(); + + int[] abc = new int[] { 0, -2, 3 }; + int[] xyz = new int[] { 0, 0, 0 }; + EIG.getExtendedFacing() + .getWorldOffset(abc, xyz); + xyz[0] += te.getXCoord(); + xyz[1] += te.getYCoord(); + xyz[2] += te.getZCoord(); + + myWorld.setBlock(xyz[0], xyz[1] - 2, xyz[2], GregTech_API.sBlockCasings4, 1, 0); + myWorld.setBlock(xyz[0], xyz[1] - 1, xyz[2], Blocks.farmland, 0, 0); + + 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(); + } + + int expectedValue = expected.getOrDefault(stackToTest, 0); + int generatedValue = generated.getOrDefault(stackToTest, 0); + + x[i] = expectedValue; + y[i] = generatedValue; + } + + 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); + } + fail(); + + } + +} diff --git a/src/functionalTest/java/kubatech/test/kubatechTestMod.java b/src/functionalTest/java/kubatech/test/kubatechTestMod.java new file mode 100644 index 00000000..bc68d3f2 --- /dev/null +++ b/src/functionalTest/java/kubatech/test/kubatechTestMod.java @@ -0,0 +1,101 @@ +package kubatech.test; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.FileSystems; +import java.nio.file.Path; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; + +import org.apache.commons.io.output.CloseShieldOutputStream; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.Mod.EventHandler; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStartedEvent; +import gregtech.GT_Mod; + +@Mod( + modid = "kubatech-tests", + name = "KubaTech Dev Tests", + version = "1.0", + dependencies = "required-after:kubatech;required-after:gregtech;after:berriespp;") +public class kubatechTestMod { + + @EventHandler + public void preInit(FMLPreInitializationEvent ev) { + // Disable GT5u messing with vanilla recipes for unit tests + GT_Mod.gregtechproxy.mNerfedWoodPlank = false; + GT_Mod.gregtechproxy.mNerfedVanillaTools = false; + } + + @EventHandler + public void onServerStarted(FMLServerStartedEvent startedEv) { + MinecraftServer.getServer() + .addChatMessage(new ChatComponentText("Running KT unit tests...")); + runTests(); + MinecraftServer.getServer() + .addChatMessage(new ChatComponentText("Running KT unit tests finished")); + } + + public void runTests() { + // https://junit.org/junit5/docs/current/user-guide/#launcher-api + System.setProperty("junit.platform.reporting.open.xml.enabled", "false"); + final Path testsXmlOutDir = FileSystems.getDefault() + .getPath("./junit-out/") + .toAbsolutePath(); + final File testsXmlOutDirFile = testsXmlOutDir.toFile(); + testsXmlOutDirFile.mkdirs(); + { + File[] fileList = testsXmlOutDirFile.listFiles(); + if (fileList != null) { + for (File child : fileList) { + if (child.isFile() && child.getName() + .endsWith(".xml")) { + child.delete(); + } + } + } + } + final LauncherDiscoveryRequest discovery = LauncherDiscoveryRequestBuilder.request() + .selectors(DiscoverySelectors.selectPackage("kubatech.test")) + .build(); + final SummaryGeneratingListener summaryGenerator = new SummaryGeneratingListener(); + final TestExecutionSummary summary; + try (PrintWriter stderrWriter = new PrintWriter(new CloseShieldOutputStream(System.err), true)) { + final LegacyXmlReportGeneratingListener xmlGenerator = new LegacyXmlReportGeneratingListener( + testsXmlOutDir, + stderrWriter); + try (LauncherSession session = LauncherFactory.openSession()) { + final Launcher launcher = session.getLauncher(); + final TestPlan plan = launcher.discover(discovery); + launcher.registerTestExecutionListeners(summaryGenerator, xmlGenerator); + launcher.execute(plan); + } + summary = summaryGenerator.getSummary(); + + summary.printFailuresTo(stderrWriter, 32); + summary.printTo(stderrWriter); + stderrWriter.flush(); + } + // Throw an exception if running via `runServer` + if (summary.getTotalFailureCount() > 0 && FMLCommonHandler.instance() + .getSide() + .isServer()) { + throw new RuntimeException("Some of the unit tests failed to execute, check the log for details"); + } + } + +} diff --git a/src/functionalTest/resources/mcmod.info b/src/functionalTest/resources/mcmod.info new file mode 100644 index 00000000..e80179bb --- /dev/null +++ b/src/functionalTest/resources/mcmod.info @@ -0,0 +1,15 @@ +[ + { + "modid":"kubatech-tests", + "name":"KubaTech Dev Tests", + "description":"KubaTech Tests to run in the development environment", + "version":"1.0", + "mcversion":"1.7.10", + "url":"https://github.com/GTNewHorizons/KubaTech", + "updateUrl":"", + "authorList":[], + "credits":"", + "logoFile":"", + "screenshots":[] + } +] 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 f43309f7..b94ceca8 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 @@ -140,7 +140,37 @@ public class GT_MetaTileEntity_ExtremeIndustrialGreenhouse extends KubaTechGTMultiBlockBase { + /*** + * BALANCE OF THE IC2 MODE: + * (let T = EIG_BALANCE_IC2_ACCELERATOR_TIER) + * All IC2 crops are simulated and all drops are generated based on the real crop drops. + * T is a tick accelerator tier for the IC2 crops, + * Each crop in the EIG is accelerated using T tier accelerator + * (Accelerators in the game are defined as 2^T acceleration, 8*(4^T) voltage, 6 amps) + * IC2 mode is unlocked at T+1 tier (glass and power) + * And each amp of T gives one crop slot, EIG only consumes 1 AMP of a tier that it is at + * (EIG starts at 4 crops (T+1 tier) and each tier quadruples the amount of slots) + * Each crop is accelerated 2^T times + * Summary: + * Accelerators in EIG are a bit cheaper than on the crop field (4 amps instead of 6 amps) + * There are 4 crops touching the accelerator (1 AMP for 1 accelerated crop) + * + * 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; + 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 CONFIGURATION_WINDOW_ID = 999; @@ -288,7 +318,9 @@ protected GT_Multiblock_Tooltip_Builder createTooltip() { .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("If there are >= 1000 crops -> Uses 1L of Weed-EX 9000 per crop per second") + .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 --------------------") @@ -309,13 +341,15 @@ protected GT_Multiblock_Tooltip_Builder createTooltip() { .addInfo("Will automatically craft seeds if they are not dropped") .addInfo("1 Fertilizer per 1 crop +200%") .addInfo("-------------------- IC2 CROPS --------------------") - .addInfo("Minimal tier: " + voltageTooltipFormatted(6)) - .addInfo("Need " + voltageTooltipFormatted(6) + " glass tier") + .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(6) + ", slots are multiplied by 4") + .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 x32 times") + .addInfo("All crops are accelerated by x" + (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER) + " times") .addInfo("1 Fertilizer per 1 crop +10%") .addInfo(StructureHologram) .addSeparator() @@ -401,8 +435,8 @@ public void construct(ItemStack itemStack, boolean b) { private void updateMaxSlots() { int tier = getVoltageTier(); - if (tier < (isIC2Mode ? 6 : 4)) mMaxSlots = 0; - else if (isIC2Mode) mMaxSlots = 4 << (2 * (tier - 6)); + 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); } @@ -418,7 +452,7 @@ public CheckRecipeResult checkProcessing() { } if (setupphase > 0) { - if ((mStorage.size() >= mMaxSlots && setupphase == 1) || (mStorage.size() == 0 && setupphase == 2)) + if ((mStorage.size() >= mMaxSlots && setupphase == 1) || (mStorage.isEmpty() && setupphase == 2)) return CheckRecipeResultRegistry.NO_RECIPE; if (setupphase == 1) { @@ -449,7 +483,7 @@ public CheckRecipeResult checkProcessing() { waterusage = 0; weedexusage = 0; for (GreenHouseSlot s : mStorage) waterusage += s.input.stackSize; - if (waterusage >= 1000) weedexusage = waterusage; + if (waterusage >= EIG_CROP_LIMIT_FOR_WEEDEX9000_REQUIREMENT) weedexusage = waterusage; waterusage *= 1000; List fluids = mInputHatches; @@ -513,12 +547,15 @@ public CheckRecipeResult checkProcessing() { double multiplier = 1.d + (((double) boost / (double) maxboost) * 4d); if (isIC2Mode) { - if (glasTier < 6) return SimpleCheckRecipeResult.ofFailure("EIG_ic2glass"); + if (glasTier < (EIG_BALANCE_IC2_ACCELERATOR_TIER + 1)) + return SimpleCheckRecipeResult.ofFailure("EIG_ic2glass"); this.mMaxProgresstime = 100; List outputs = new ArrayList<>(); for (int i = 0; i < Math.min(mMaxSlots, mStorage.size()); i++) outputs.addAll( mStorage.get(i) - .getIC2Drops(this, ((double) this.mMaxProgresstime * 32d) * multiplier)); + .getIC2Drops( + this, + ((double) this.mMaxProgresstime * (1 << EIG_BALANCE_IC2_ACCELERATOR_TIER)) * multiplier)); this.mOutputItems = outputs.toArray(new ItemStack[0]); } else { this.mMaxProgresstime = Math.max(20, 100 / (tier - 3)); // Min 1 s @@ -552,7 +589,7 @@ public boolean checkMachine(IGregTechTileEntity iGregTechTileEntity, ItemStack i for (GT_MetaTileEntity_Hatch_Energy hatchEnergy : this.mEnergyHatches) if (this.glasTier < hatchEnergy.mTier) return false; - boolean valid = this.mMaintenanceHatches.size() == 1 && this.mEnergyHatches.size() >= 1 && this.mCasing >= 70; + boolean valid = this.mMaintenanceHatches.size() == 1 && !this.mEnergyHatches.isEmpty() && this.mCasing >= 70; if (valid) updateMaxSlots(); @@ -980,14 +1017,16 @@ private ItemStack addCrop(ItemStack input) { final Map dropprogress = new HashMap<>(); - private static class GreenHouseSlot extends InventoryCrafting { + public static class GreenHouseSlot extends InventoryCrafting { + + private static final int NUMBER_OF_GENERATIONS_TO_MAKE = 10; final ItemStack input; Block crop; ArrayList customDrops = null; ItemStack undercrop = null; List drops; - boolean isValid; + public boolean isValid; boolean isIC2Crop; boolean noHumidity; int growthticks; @@ -1259,6 +1298,14 @@ public void recalculate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity 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); @@ -1290,9 +1337,11 @@ public void recalculate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity // GENERATE DROPS generations = new ArrayList<>(); - out: for (int i = 0; i < 10; i++) // get 10 generations + 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; @@ -1303,15 +1352,21 @@ public void recalculate(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity rn = new Random(); // CHECK GROWTH SPEED - te.humidity = (byte) (noHumidity ? 0 : 12); // humidity with full water storage or 0 humidity - te.airQuality = 6; // air quality when sky is seen - te.nutrients = 8; // nutrients with full nutrient storage - - int dur = cc.growthDuration(te); - int rate = te.calcGrowthRate(); - if (rate == 0) return; // should not be possible with those stats - growthticks = (int) Math.ceil( - ((double) dur / (double) rate) * (double) cc.maxSize() * (double) TileEntityCrop.tickRate); + + 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--; @@ -1337,7 +1392,7 @@ public List getDrops() { public List getIC2Drops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse tileEntity, double timeelapsed) { - int r = rn.nextInt(10); + int r = rn.nextInt(NUMBER_OF_GENERATIONS_TO_MAKE); if (generations.size() <= r) return new ArrayList<>(); double growthPercent = (timeelapsed / (double) growthticks); List generation = generations.get(r); @@ -1365,7 +1420,7 @@ public List getIC2Drops(GT_MetaTileEntity_ExtremeIndustrialGreenhouse public int addDrops(World world, int count) { if (drops == null) drops = new ArrayList<>(); - if (customDrops != null && customDrops.size() > 0) { + if (customDrops != null && !customDrops.isEmpty()) { @SuppressWarnings("unchecked") ArrayList d = (ArrayList) customDrops.clone(); for (ItemStack x : drops) {