diff --git a/.editorconfig b/.editorconfig index 61a63aa508c..b0a6ecee059 100644 --- a/.editorconfig +++ b/.editorconfig @@ -125,7 +125,7 @@ ij_java_keep_first_column_comment = false ij_java_keep_indents_on_empty_lines = false ij_java_keep_line_breaks = true ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = true ij_java_keep_simple_classes_in_one_line = true ij_java_keep_simple_lambdas_in_one_line = true ij_java_keep_simple_methods_in_one_line = true diff --git a/build.gradle b/build.gradle index 5e4447c6c45..29b2ba602bf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -//version: 1701739812 +//version: 1702244235 /* * DO NOT CHANGE THIS FILE! * Also, you may replace this file at any time if there is an update available. @@ -79,6 +79,7 @@ propertyDefaultIfUnset("curseForgeRelations", "") propertyDefaultIfUnsetWithEnvVar("releaseType", "release", "RELEASE_TYPE") propertyDefaultIfUnset("generateDefaultChangelog", false) propertyDefaultIfUnset("customMavenPublishUrl", "") +propertyDefaultIfUnset("mavenArtifactGroup", getDefaultArtifactGroup()) propertyDefaultIfUnset("enableModernJavaSyntax", false) propertyDefaultIfUnset("enableSpotless", false) propertyDefaultIfUnset("enableJUnit", false) @@ -984,8 +985,8 @@ if (customMavenPublishUrl) { } // providers is not available here, use System for getting env vars - groupId = System.getenv('ARTIFACT_GROUP_ID') ?: project.group - artifactId = System.getenv('ARTIFACT_ID') ?: project.name + groupId = System.getenv('ARTIFACT_GROUP_ID') ?: project.mavenArtifactGroup + artifactId = System.getenv('ARTIFACT_ID') ?: project.modArchivesBaseName version = System.getenv('RELEASE_VERSION') ?: publishedVersion } } @@ -1130,6 +1131,11 @@ tasks.register('faq') { // Helpers +def getDefaultArtifactGroup() { + def lastIndex = project.modGroup.lastIndexOf('.') + return lastIndex < 0 ? project.modGroup : project.modGroup.substring(0, lastIndex) +} + def getFile(String relativePath) { return new File(projectDir, relativePath) } diff --git a/dependencies.gradle b/dependencies.gradle index 3af530e782d..82ef07ce6a8 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -25,13 +25,14 @@ dependencies { // the CCL deobf jar uses very old MCP mappings, making it error at runtime in runClient/runServer // therefore we manually deobf the regular jar implementation rfg.deobf("curse.maven:codechicken-lib-1-8-242818:2779848") // CCL 3.2.3.358 + implementation("com.cleanroommc:modularui:2.4.1") { transitive = false } // Soft Dependencies // Can change any of these from compileOnlyApi -> implementation to test them in-game. implementation "CraftTweaker2:CraftTweaker2-MC1120-Main:1.12-4.1.20.684" implementation rfg.deobf("curse.maven:ctm-267602:2915363") // CTM 1.0.2.31 - implementation rfg.deobf("curse.maven:groovyscript-687577:4905039") // GRS 0.7.0 + implementation("com.cleanroommc:groovyscript:0.7.1") { transitive = false } implementation rfg.deobf("curse.maven:ae2-extended-life-570458:4402048") // AE2UEL 0.55.6 compileOnlyApi rfg.deobf("curse.maven:opencomputers-223008:4526246") // OpenComputers 1.8.0+9833087 @@ -41,6 +42,7 @@ dependencies { compileOnlyApi rfg.deobf("curse.maven:hwyla-253449:2568751") // HWYLA 1.8.26-B41 compileOnlyApi rfg.deobf("curse.maven:baubles-227083:2518667") // Baubles 1.5.2 compileOnlyApi rfg.deobf("curse.maven:forestry-59751:2684780") // Forestry 5.8.2.387 + compileOnlyApi rfg.deobf("curse.maven:chisel-235279:2915375") // Chisel 1.0.2.45 // Mods with Soft compat but which have no need to be in code, such as isModLoaded() checks and getModItem() recipes. // Uncomment any of these to test them in-game. diff --git a/gradle.properties b/gradle.properties index e7b28291e09..8cc69185de4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ modGroup = gregtech # Version of your mod. # This field can be left empty if you want your mod's version to be determined by the latest git tag instead. -modVersion = 2.8.2-beta +modVersion = 2.8.5-beta # Whether to use the old jar naming structure (modid-mcversion-version) instead of the new version (modid-version) includeMCVersionJar = true @@ -52,6 +52,8 @@ accessTransformersFile = gregtech_at.cfg usesMixins = false # Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail! mixinsPackage = +# Automatically generates a mixin config json if enabled, with the name mixins.modid.json +generateMixinConfig=false # Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! # Example value: coreModClass = asm.FMLPlugin + modGroup = com.myname.mymodid -> com.myname.mymodid.asm.FMLPlugin coreModClass = asm.GregTechLoadingPlugin @@ -75,6 +77,7 @@ includeWellKnownRepositories = true # Overrides the above setting to be always true, as these repositories are needed to fetch the mods includeCommonDevEnvMods = true + # If enabled, you may use 'shadowCompile' for dependencies. They will be integrated in your jar. It is your # responsibility check the licence and request permission for distribution, if required. usesShadowedDependencies = false @@ -117,9 +120,10 @@ curseForgeProjectId = # Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], # and the name is the CurseForge project slug of the other mod. # Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft -curseForgeRelations = requiredDependency:codechicken-lib-1-8;incompatible:gregtechce +curseForgeRelations = requiredDependency:codechicken-lib-1-8;requiredDependency:modularui;requiredDependency:mixin-booter;incompatible:gregtechce # This project's release type on CurseForge and/or Modrinth +# Alternatively this can be set with the 'RELEASE_TYPE' environment variable. # Allowed types: release, beta, alpha releaseType = beta @@ -129,6 +133,17 @@ generateDefaultChangelog = false # Prevent the source code from being published noPublishedSources = false +# Publish to a custom maven location. Follows a few rules: +# Group ID can be set with the 'ARTIFACT_GROUP_ID' environment variable, default to 'project.group' +# Artifact ID can be set with the 'ARTIFACT_ID' environment variable, default to 'project.name' +# Version can be set with the 'RELEASE_VERSION' environment variable, default to 'modVersion' +# For maven credentials: +# Username is set with the 'MAVEN_USER' environment variable, default to "NONE" +# Password is set with the 'MAVEN_PASSWORD' environment variable, default to "NONE" +customMavenPublishUrl= +# The group for maven artifacts. Defaults to the 'project.modGroup' until the last '.' (if any). +# So 'mymod' becomes 'mymod' and 'com.myname.mymodid' 'becomes com.myname' +mavenArtifactGroup= # Enable spotless checks # Enforces code formatting on your source code diff --git a/src/api/java/forestry/api/arboriculture/IToolGrafter.java b/src/api/java/forestry/api/arboriculture/IToolGrafter.java deleted file mode 100644 index cd25031ac20..00000000000 --- a/src/api/java/forestry/api/arboriculture/IToolGrafter.java +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************* - * Copyright 2011-2014 SirSengir - * - * This work (the API) is licensed under the "MIT" License, see LICENSE.txt for details. - ******************************************************************************/ -package forestry.api.arboriculture; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public interface IToolGrafter { - /** - * Called by leaves to determine the increase in sapling droprate. - * - * @param stack ItemStack containing the grafter. - * @param world Minecraft world the player and the target block inhabit. - * @param pos Coordinate of the broken leaf block. - * @return Float representing the factor the usual drop chance is to be multiplied by. - */ - float getSaplingModifier(ItemStack stack, World world, EntityPlayer player, BlockPos pos); -} diff --git a/src/api/java/mods/railcraft/api/items/IToolCrowbar.java b/src/api/java/mods/railcraft/api/items/IToolCrowbar.java new file mode 100644 index 00000000000..7b8006b5ec2 --- /dev/null +++ b/src/api/java/mods/railcraft/api/items/IToolCrowbar.java @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------------ + Copyright (c) CovertJaguar, 2011-2020 + + This work (the API) is licensed under the "MIT" License, + see LICENSE.md for details. + -----------------------------------------------------------------------------*/ +package mods.railcraft.api.items; + +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; + +/** + * @author CovertJaguar + */ +public interface IToolCrowbar { + String ORE_TAG = "toolCrowbar"; + + /** + * Controls non-rotational interactions with blocks. Crowbar specific stuff. + *

+ * Rotational interaction is handled by the Block.rotateBlock() function, + * which should be called from the Item.onUseFirst() function of your tool. + * + * @param player the player + * @param crowbar the crowbar + * @param pos the block @return true if can whack a block + */ + boolean canWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos); + + /** + * Callback to do damage to the item. + * + * @param player the player + * @param crowbar the crowbar + * @param pos the block + */ + void onWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos); + + /** + * Controls whether you can link a cart. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart @return true if can link a cart + */ + boolean canLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Callback to do damage. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart + */ + void onLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Controls whether you can boost a cart. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart @return true if can boost a cart + */ + boolean canBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Callback to do damage, boosting a cart usually does more damage than + * normal usage. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart + */ + void onBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); +} diff --git a/src/api/java/mrtjp/projectred/api/IScrewdriver.java b/src/api/java/mrtjp/projectred/api/IScrewdriver.java new file mode 100644 index 00000000000..a6298fecefd --- /dev/null +++ b/src/api/java/mrtjp/projectred/api/IScrewdriver.java @@ -0,0 +1,11 @@ +package mrtjp.projectred.api; + +import net.minecraft.item.ItemStack; +import net.minecraft.entity.player.EntityPlayer; + +public interface IScrewdriver { + + boolean canUse(EntityPlayer player, ItemStack stack); + + void damageScrewdriver(EntityPlayer player, ItemStack stack); // Damage the item on usage +} diff --git a/src/main/java/gregtech/GregTechMod.java b/src/main/java/gregtech/GregTechMod.java index 1942c3a0c9f..f76989a97d5 100644 --- a/src/main/java/gregtech/GregTechMod.java +++ b/src/main/java/gregtech/GregTechMod.java @@ -3,9 +3,7 @@ import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.modules.ModuleContainerRegistryEvent; -import gregtech.api.util.oreglob.OreGlob; import gregtech.client.utils.BloomEffectUtil; -import gregtech.common.covers.filter.oreglob.impl.OreGlobParser; import gregtech.modules.GregTechModules; import gregtech.modules.ModuleManager; @@ -15,16 +13,27 @@ import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; -import net.minecraftforge.fml.common.event.*; +import net.minecraftforge.fml.common.event.FMLConstructionEvent; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLInterModComms; +import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.event.FMLServerStartingEvent; +import net.minecraftforge.fml.common.event.FMLServerStoppedEvent; +import net.minecraftforge.fml.common.event.FMLServerStoppingEvent; @Mod(modid = GTValues.MODID, name = "GregTech", acceptedMinecraftVersions = "[1.12.2,1.13)", version = GTInternalTags.VERSION, dependencies = "required:forge@[14.23.5.2847,);" + "required-after:codechickenlib@[3.2.3,);" + - "after:appliedenergistics2;" + "after:forestry;" + "after:extrabees;" + "after:extratrees;" + - "after:genetics;" + "after:magicbees;" + "after:jei@[4.15.0,);" + "after:crafttweaker@[4.1.20,);" + - "after:groovyscript@[0.7.0,);" + "after:theoneprobe;" + "after:hwyla;") + "required-after:modularui@[2.3,);" + "required-after:mixinbooter@[8.0,);" + "after:appliedenergistics2;" + + "after:forestry;" + "after:extrabees;" + "after:extratrees;" + "after:genetics;" + "after:magicbees;" + + "after:jei@[4.15.0,);" + "after:crafttweaker@[4.1.20,);" + "after:groovyscript@[0.7.0,);" + + "after:theoneprobe;" + "after:hwyla;") public class GregTechMod { // Hold this so that we can reference non-interface methods without @@ -43,7 +52,6 @@ public GregTechMod() { public void onConstruction(FMLConstructionEvent event) { moduleManager = ModuleManager.getInstance(); GregTechAPI.moduleManager = moduleManager; - OreGlob.setCompiler(input -> new OreGlobParser(input).compile()); moduleManager.registerContainer(new GregTechModules()); MinecraftForge.EVENT_BUS.post(new ModuleContainerRegistryEvent()); moduleManager.setup(event.getASMHarvestedData(), Loader.instance().getConfigDir()); diff --git a/src/main/java/gregtech/api/GTValues.java b/src/main/java/gregtech/api/GTValues.java index fa0f424854a..7a1b5d34f4f 100644 --- a/src/main/java/gregtech/api/GTValues.java +++ b/src/main/java/gregtech/api/GTValues.java @@ -155,7 +155,10 @@ public class GTValues { MODID_ET = "extratrees", MODID_GENETICS = "genetics", MODID_BOP = "biomesoplenty", - MODID_TCON = "tconstruct"; + MODID_TCON = "tconstruct", + MODID_PROJRED_CORE = "projectred-core", + MODID_RC = "railcraft", + MODID_CHISEL = "chisel"; private static Boolean isClient; diff --git a/src/main/java/gregtech/api/GregTechAPI.java b/src/main/java/gregtech/api/GregTechAPI.java index 14188ff6bfa..f3279eb5b3b 100644 --- a/src/main/java/gregtech/api/GregTechAPI.java +++ b/src/main/java/gregtech/api/GregTechAPI.java @@ -67,6 +67,7 @@ public class GregTechAPI { public static final GTControlledRegistry MTE_REGISTRY = new GTControlledRegistry<>( Short.MAX_VALUE); + @Deprecated public static final GTControlledRegistry UI_FACTORY_REGISTRY = new GTControlledRegistry<>( Short.MAX_VALUE); public static final GTControlledRegistry COVER_REGISTRY = new GTControlledRegistry<>( diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java index bde46b5b611..ea548fe15ce 100644 --- a/src/main/java/gregtech/api/capability/GregtechDataCodes.java +++ b/src/main/java/gregtech/api/capability/GregtechDataCodes.java @@ -26,10 +26,6 @@ public static int assignId() { // Drum public static final int UPDATE_AUTO_OUTPUT = assignId(); - // Safe - public static final int UPDATE_LOCKED_STATE = assignId(); - public static final int UPDATE_CONTENTS_SEED = assignId(); - // Steam Machines public static final int NEEDS_VENTING = assignId(); public static final int VENTING_SIDE = assignId(); diff --git a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java index a241b32b3a0..cc2ce878aee 100644 --- a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java @@ -651,8 +651,7 @@ protected int[] calculateOverclock(@NotNull Recipe recipe) { * @param recipe the recipe to overclock * @return an int array of {OverclockedEUt, OverclockedDuration} */ - @NotNull - protected int[] performOverclocking(@NotNull Recipe recipe) { + protected int @NotNull [] performOverclocking(@NotNull Recipe recipe) { int[] values = { recipe.getEUt(), recipe.getDuration(), getNumberOfOCs(recipe.getEUt()) }; modifyOverclockPre(values, recipe.getRecipePropertyStorage()); diff --git a/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java b/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java index 81972c3f5d7..37688a4deec 100644 --- a/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java +++ b/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java @@ -157,16 +157,16 @@ private boolean handleElectricItem(IElectricItem electricItem) { // Check if the item is a battery (or similar), and if we can receive some amount of energy if (electricItem.canProvideChargeExternally() && getEnergyCanBeInserted() > 0) { - // Drain from the battery if we are below half energy capacity, and if the tier matches - if (chargePercent <= 0.5 && chargeTier == machineTier) { + // Drain from the battery if we are below 1/3rd energy capacity, and if the tier matches + if (chargePercent <= 0.33 && chargeTier == machineTier) { long dischargedBy = electricItem.discharge(getEnergyCanBeInserted(), machineTier, false, true, false); addEnergy(dischargedBy); return dischargedBy > 0L; } } - // Else, check if we have above 50% power - if (chargePercent > 0.5) { + // Else, check if we have above 2/3rds charge + if (chargePercent > 0.66) { long chargedBy = electricItem.charge(getEnergyStored(), chargeTier, false, false); removeEnergy(chargedBy); return chargedBy > 0; @@ -178,7 +178,7 @@ private boolean handleForgeEnergyItem(IEnergyStorage energyStorage) { int machineTier = GTUtility.getTierByVoltage(Math.max(getInputVoltage(), getOutputVoltage())); double chargePercent = getEnergyStored() / (getEnergyCapacity() * 1.0); - if (chargePercent > 0.5) { + if (chargePercent > 0.66) { // 2/3rds full long chargedBy = FeCompat.insertEu(energyStorage, GTValues.V[machineTier]); removeEnergy(chargedBy); return chargedBy > 0; diff --git a/src/main/java/gregtech/api/capability/impl/FluidHandlerDelegate.java b/src/main/java/gregtech/api/capability/impl/FluidHandlerDelegate.java index f6e2a369aba..0449a6b3dfb 100644 --- a/src/main/java/gregtech/api/capability/impl/FluidHandlerDelegate.java +++ b/src/main/java/gregtech/api/capability/impl/FluidHandlerDelegate.java @@ -4,13 +4,14 @@ import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidTankProperties; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class FluidHandlerDelegate implements IFluidHandler { public final IFluidHandler delegate; - public FluidHandlerDelegate(IFluidHandler delegate) { + public FluidHandlerDelegate(@NotNull IFluidHandler delegate) { this.delegate = delegate; } diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java index 3917d4a7bda..1ebb3e73c82 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java @@ -282,7 +282,7 @@ protected boolean prepareRecipeDistinct(Recipe recipe) { } @Override - protected void modifyOverclockPre(@NotNull int[] values, @NotNull IRecipePropertyStorage storage) { + protected void modifyOverclockPre(int @NotNull [] values, @NotNull IRecipePropertyStorage storage) { super.modifyOverclockPre(values, storage); // apply maintenance bonuses diff --git a/src/main/java/gregtech/api/cover/CoverUIFactory.java b/src/main/java/gregtech/api/cover/CoverUIFactory.java index a21cd057d44..1716cbeda27 100644 --- a/src/main/java/gregtech/api/cover/CoverUIFactory.java +++ b/src/main/java/gregtech/api/cover/CoverUIFactory.java @@ -13,6 +13,7 @@ import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +@Deprecated public final class CoverUIFactory extends UIFactory { public static final CoverUIFactory INSTANCE = new CoverUIFactory(); diff --git a/src/main/java/gregtech/api/cover/CoverWithUI.java b/src/main/java/gregtech/api/cover/CoverWithUI.java index eb7dfcad582..10ab6f243dd 100644 --- a/src/main/java/gregtech/api/cover/CoverWithUI.java +++ b/src/main/java/gregtech/api/cover/CoverWithUI.java @@ -2,17 +2,57 @@ import gregtech.api.gui.IUIHolder; import gregtech.api.gui.ModularUI; +import gregtech.api.mui.GTGuiTheme; +import gregtech.api.mui.GregTechGuiScreen; +import gregtech.api.mui.factory.CoverGuiFactory; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; -public interface CoverWithUI extends Cover, IUIHolder { +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.value.sync.GuiSyncManager; +import org.jetbrains.annotations.ApiStatus; + +public interface CoverWithUI extends Cover, IUIHolder, IGuiHolder { + + @ApiStatus.Experimental + default boolean usesMui2() { + return false; + } default void openUI(EntityPlayerMP player) { - CoverUIFactory.INSTANCE.openUI(this, player); + if (usesMui2()) { + CoverGuiFactory.open(player, this); + } else { + CoverUIFactory.INSTANCE.openUI(this, player); + } + } + + @Deprecated + default ModularUI createUI(EntityPlayer player) { + return null; + } + + @ApiStatus.NonExtendable + @SideOnly(Side.CLIENT) + @Override + default ModularScreen createScreen(SidedPosGuiData guiData, ModularPanel mainPanel) { + return new GregTechGuiScreen(mainPanel, getUITheme()); + } + + default GTGuiTheme getUITheme() { + return GTGuiTheme.STANDARD; } - ModularUI createUI(EntityPlayer player); + @Override + default ModularPanel buildUI(SidedPosGuiData guiData, GuiSyncManager guiSyncManager) { + return null; + } @Override default boolean isValid() { diff --git a/src/main/java/gregtech/api/fluids/FluidBuilder.java b/src/main/java/gregtech/api/fluids/FluidBuilder.java index 757369b75ae..f7a75111cc4 100644 --- a/src/main/java/gregtech/api/fluids/FluidBuilder.java +++ b/src/main/java/gregtech/api/fluids/FluidBuilder.java @@ -428,7 +428,7 @@ private void determineTemperature(@Nullable Material material) { } case GAS -> ROOM_TEMPERATURE; case PLASMA -> { - if (material.hasFluid()) { + if (material.hasFluid() && material.getFluid() != null) { yield BASE_PLASMA_TEMPERATURE + material.getFluid().getTemperature(); } yield BASE_PLASMA_TEMPERATURE; diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorage.java b/src/main/java/gregtech/api/fluids/store/FluidStorage.java index f9a232b0d27..fe7505423d0 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorage.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorage.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Comparator; import java.util.Map; public final class FluidStorage { @@ -69,12 +70,14 @@ public void registerFluids(@NotNull Material material) { enqueueRegistration(FluidStorageKeys.LIQUID, new FluidBuilder()); } - for (var entry : toRegister.entrySet()) { - Fluid fluid = entry.getValue().build(material.getModid(), material, entry.getKey()); - if (!storeNoOverwrites(entry.getKey(), fluid)) { - GTLog.logger.error("{} already has an associated fluid for material {}", material); - } - } + toRegister.entrySet().stream() + .sorted(Comparator.comparingInt(e -> -e.getKey().getRegistrationPriority())) + .forEach(entry -> { + Fluid fluid = entry.getValue().build(material.getModid(), material, entry.getKey()); + if (!storeNoOverwrites(entry.getKey(), fluid)) { + GTLog.logger.error("{} already has an associated fluid for material {}", material); + } + }); toRegister = null; registered = true; } diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java b/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java index 01f44dcc8da..7b58514aa35 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java @@ -24,6 +24,7 @@ public final class FluidStorageKey { private final Function translationKeyFunction; private final int hashCode; private final FluidState defaultFluidState; + private final int registrationPriority; public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull MaterialIconType iconType, @NotNull UnaryOperator<@NotNull String> registryNameOperator, @@ -35,12 +36,20 @@ public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull Mate @NotNull UnaryOperator<@NotNull String> registryNameOperator, @NotNull Function<@NotNull Material, @NotNull String> translationKeyFunction, @Nullable FluidState defaultFluidState) { + this(resourceLocation, iconType, registryNameOperator, translationKeyFunction, defaultFluidState, 0); + } + + public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull MaterialIconType iconType, + @NotNull UnaryOperator<@NotNull String> registryNameOperator, + @NotNull Function<@NotNull Material, @NotNull String> translationKeyFunction, + @Nullable FluidState defaultFluidState, int registrationPriority) { this.resourceLocation = resourceLocation; this.iconType = iconType; this.registryNameOperator = registryNameOperator; this.translationKeyFunction = translationKeyFunction; this.hashCode = resourceLocation.hashCode(); this.defaultFluidState = defaultFluidState; + this.registrationPriority = registrationPriority; if (keys.containsKey(resourceLocation)) { throw new IllegalArgumentException("Cannot create duplicate keys"); } @@ -81,6 +90,14 @@ public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull Mate return defaultFluidState; } + /** + * @return The registration priority for this fluid type, determining the build order for fluids. + * Useful for when your fluid building requires some properties from previous fluids. + */ + public int getRegistrationPriority() { + return registrationPriority; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java b/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java index 10407e03b03..3b9a6be51e8 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java @@ -33,7 +33,7 @@ public final class FluidStorageKeys { public static final FluidStorageKey PLASMA = new FluidStorageKey(gregtechId("plasma"), MaterialIconType.plasma, s -> "plasma." + s, m -> "gregtech.fluid.plasma", - FluidState.PLASMA); + FluidState.PLASMA, -1); private FluidStorageKeys() {} } diff --git a/src/main/java/gregtech/api/gui/GuiTextures.java b/src/main/java/gregtech/api/gui/GuiTextures.java index fe90bbbdf99..decdeeb7c05 100644 --- a/src/main/java/gregtech/api/gui/GuiTextures.java +++ b/src/main/java/gregtech/api/gui/GuiTextures.java @@ -21,8 +21,10 @@ public class GuiTextures { // BASE TEXTURES public static final TextureArea BACKGROUND = AdoptableTextureArea.fullImage("textures/gui/base/background.png", 176, 166, 3, 3); + // todo try to remove public static final TextureArea BORDERED_BACKGROUND = AdoptableTextureArea .fullImage("textures/gui/base/bordered_background.png", 195, 136, 4, 4); + // todo try to remove public static final TextureArea BOXED_BACKGROUND = AdoptableTextureArea .fullImage("textures/gui/base/boxed_background.png", 256, 174, 11, 11); public static final SteamTexture BACKGROUND_STEAM = SteamTexture.fullImage("textures/gui/base/background_%s.png", @@ -377,8 +379,6 @@ public class GuiTextures { .fullImage("textures/gui/progress_bar/progress_bar_slice.png"); public static final SteamTexture PROGRESS_BAR_SOLAR_STEAM = SteamTexture .fullImage("textures/gui/progress_bar/progress_bar_solar_%s.png"); - public static final TextureArea PROGRESS_BAR_UNLOCK = TextureArea - .fullImage("textures/gui/progress_bar/progress_bar_unlock.png"); public static final TextureArea PROGRESS_BAR_UNPACKER = TextureArea .fullImage("textures/gui/progress_bar/progress_bar_unpacker.png"); public static final TextureArea PROGRESS_BAR_WIREMILL = TextureArea @@ -437,6 +437,10 @@ public class GuiTextures { .fullImage("textures/items/metaitems/cover.controller.png"); // Ore Filter + public static final TextureArea ORE_FILTER_BUTTON_CASE_SENSITIVE = TextureArea + .fullImage("textures/gui/widget/ore_filter/button_case_sensitive.png"); + public static final TextureArea ORE_FILTER_BUTTON_MATCH_ALL = TextureArea + .fullImage("textures/gui/widget/ore_filter/button_match_all.png"); public static final TextureArea ORE_FILTER_INFO = TextureArea.fullImage("textures/gui/widget/ore_filter/info.png"); public static final TextureArea ORE_FILTER_SUCCESS = TextureArea .fullImage("textures/gui/widget/ore_filter/success.png"); diff --git a/src/main/java/gregtech/api/gui/package-info.java b/src/main/java/gregtech/api/gui/package-info.java new file mode 100644 index 00000000000..f17b0c3af12 --- /dev/null +++ b/src/main/java/gregtech/api/gui/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package gregtech.api.gui; diff --git a/src/main/java/gregtech/api/items/gui/ItemUIFactory.java b/src/main/java/gregtech/api/items/gui/ItemUIFactory.java index 796cfeaf535..abff0448370 100644 --- a/src/main/java/gregtech/api/items/gui/ItemUIFactory.java +++ b/src/main/java/gregtech/api/items/gui/ItemUIFactory.java @@ -2,14 +2,44 @@ import gregtech.api.gui.ModularUI; import gregtech.api.items.metaitem.stats.IItemComponent; +import gregtech.api.mui.GTGuiTheme; +import gregtech.api.mui.GregTechGuiScreen; import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; -public interface ItemUIFactory extends IItemComponent { +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.HandGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.value.sync.GuiSyncManager; +import org.jetbrains.annotations.ApiStatus; + +public interface ItemUIFactory extends IItemComponent, IGuiHolder { /** * Creates new UI basing on given holder. Holder contains information * about item stack and hand, and also player */ - ModularUI createUI(PlayerInventoryHolder holder, EntityPlayer entityPlayer); + @Deprecated + default ModularUI createUI(PlayerInventoryHolder holder, EntityPlayer entityPlayer) { + return null; + } + + @ApiStatus.NonExtendable + @SideOnly(Side.CLIENT) + @Override + default ModularScreen createScreen(HandGuiData creationContext, ModularPanel mainPanel) { + return new GregTechGuiScreen(mainPanel, getUITheme()); + } + + default GTGuiTheme getUITheme() { + return GTGuiTheme.STANDARD; + } + + @Override + default ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) { + return null; + } } diff --git a/src/main/java/gregtech/api/items/gui/PlayerInventoryUIFactory.java b/src/main/java/gregtech/api/items/gui/PlayerInventoryUIFactory.java index e9be886107d..146d3bcb54c 100644 --- a/src/main/java/gregtech/api/items/gui/PlayerInventoryUIFactory.java +++ b/src/main/java/gregtech/api/items/gui/PlayerInventoryUIFactory.java @@ -19,6 +19,7 @@ /** * {@link UIFactory} implementation for {@link MetaItem}s */ +@Deprecated public class PlayerInventoryUIFactory extends UIFactory { public static final PlayerInventoryUIFactory INSTANCE = new PlayerInventoryUIFactory(); diff --git a/src/main/java/gregtech/api/items/toolitem/IGTTool.java b/src/main/java/gregtech/api/items/toolitem/IGTTool.java index 300c36f537e..4c3ba369341 100644 --- a/src/main/java/gregtech/api/items/toolitem/IGTTool.java +++ b/src/main/java/gregtech/api/items/toolitem/IGTTool.java @@ -40,6 +40,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.item.EntityMinecart; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.EntityEquipmentSlot; @@ -68,6 +69,8 @@ import forestry.api.arboriculture.IToolGrafter; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import mods.railcraft.api.items.IToolCrowbar; +import mrtjp.projectred.api.IScrewdriver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -91,10 +94,12 @@ @Optional.Interface(modid = GTValues.MODID_COFH, iface = "cofh.api.item.IToolHammer"), @Optional.Interface(modid = GTValues.MODID_EIO, iface = "crazypants.enderio.api.tool.ITool"), @Optional.Interface(modid = GTValues.MODID_FR, iface = "forestry.api.arboriculture.IToolGrafter"), + @Optional.Interface(modid = GTValues.MODID_PROJRED_CORE, iface = "mrtjp.projectred.api.IScrewdriver"), + @Optional.Interface(modid = GTValues.MODID_RC, iface = "mods.railcraft.api.items.IToolCrowbar"), @Optional.Interface(modid = GTValues.MODID_ECORE, iface = "com.enderio.core.common.interfaces.IOverlayRenderAware") }) public interface IGTTool extends ItemUIFactory, IAEWrench, IToolWrench, IToolHammer, ITool, IToolGrafter, - IOverlayRenderAware { + IOverlayRenderAware, IScrewdriver, IToolCrowbar { /** * @return the modid of the tool @@ -1042,4 +1047,47 @@ default float getSaplingModifier(ItemStack stack, World world, EntityPlayer play default void renderItemOverlayIntoGUI(@NotNull ItemStack stack, int xPosition, int yPosition) { ToolChargeBarRenderer.renderBarsTool(this, stack, xPosition, yPosition); } + + // IScrewdriver + @Override + default boolean canUse(EntityPlayer player, ItemStack stack) { + return get().getToolClasses(stack).contains(ToolClasses.SCREWDRIVER); + } + + @Override + default void damageScrewdriver(EntityPlayer player, ItemStack stack) { + damageItem(stack, player); + } + + // IToolCrowbar + + @Override + default boolean canWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos) { + damageItem(crowbar, player); + } + + @Override + default boolean canLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + damageItem(crowbar, player); + } + + @Override + default boolean canBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + damageItem(crowbar, player); + } } diff --git a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java index 1412a674817..659460902b0 100644 --- a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java @@ -15,6 +15,9 @@ import gregtech.api.items.toolitem.ToolHelper; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.metatileentity.interfaces.ISyncedTileEntity; +import gregtech.api.mui.GTGuiTheme; +import gregtech.api.mui.GregTechGuiScreen; +import gregtech.api.mui.factory.MetaTileEntityGuiFactory; import gregtech.api.recipes.RecipeMap; import gregtech.api.util.GTLog; import gregtech.api.util.GTTransferUtils; @@ -66,6 +69,11 @@ import codechicken.lib.texture.TextureUtils; import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.PosGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.value.sync.GuiSyncManager; import com.google.common.base.Preconditions; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -82,7 +90,7 @@ import static gregtech.api.capability.GregtechDataCodes.*; -public abstract class MetaTileEntity implements ISyncedTileEntity, CoverHolder, IVoidable { +public abstract class MetaTileEntity implements ISyncedTileEntity, CoverHolder, IVoidable, IGuiHolder { public static final IndexedCuboid6 FULL_CUBE_COLLISION = new IndexedCuboid6(null, Cuboid6.full); @@ -412,12 +420,36 @@ protected boolean openGUIOnRightClick() { * @param entityPlayer player opening inventory * @return freshly created UI instance */ - protected abstract ModularUI createUI(EntityPlayer entityPlayer); + @Deprecated + protected ModularUI createUI(EntityPlayer entityPlayer) { + return null; + } + @Deprecated public ModularUI getModularUI(EntityPlayer entityPlayer) { return createUI(entityPlayer); } + @ApiStatus.Experimental + public boolean usesMui2() { + return false; + } + + @SideOnly(Side.CLIENT) + @Override + public final ModularScreen createScreen(PosGuiData posGuiData, ModularPanel mainPanel) { + return new GregTechGuiScreen(mainPanel, getUITheme()); + } + + public GTGuiTheme getUITheme() { + return GTGuiTheme.STANDARD; + } + + @Override + public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) { + return null; + } + public final void onCoverLeftClick(EntityPlayer playerIn, CuboidRayTraceResult result) { Cover cover = getCoverAtSide(result.sideHit); if (cover == null || !cover.onLeftClick(playerIn, result)) { @@ -435,7 +467,11 @@ public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing fac ItemStack heldStack = playerIn.getHeldItem(hand); if (!playerIn.isSneaking() && openGUIOnRightClick()) { if (getWorld() != null && !getWorld().isRemote) { - MetaTileEntityUIFactory.INSTANCE.openUI(getHolder(), (EntityPlayerMP) playerIn); + if (usesMui2()) { + MetaTileEntityGuiFactory.open(playerIn, this); + } else { + MetaTileEntityUIFactory.INSTANCE.openUI(getHolder(), (EntityPlayerMP) playerIn); + } } return true; } else { diff --git a/src/main/java/gregtech/api/metatileentity/MetaTileEntityUIFactory.java b/src/main/java/gregtech/api/metatileentity/MetaTileEntityUIFactory.java index 43dd60efea2..8486ddf30c4 100644 --- a/src/main/java/gregtech/api/metatileentity/MetaTileEntityUIFactory.java +++ b/src/main/java/gregtech/api/metatileentity/MetaTileEntityUIFactory.java @@ -15,6 +15,7 @@ /** * {@link UIFactory} implementation for {@link MetaTileEntity} */ +@Deprecated public class MetaTileEntityUIFactory extends UIFactory { public static final MetaTileEntityUIFactory INSTANCE = new MetaTileEntityUIFactory(); diff --git a/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java b/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java index 473b950d63c..97200ab8518 100644 --- a/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java +++ b/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java @@ -7,6 +7,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,24 +29,34 @@ protected void invalidateNeighbors() { } } + @MustBeInvokedByOverriders @Override public void setWorld(@NotNull World worldIn) { super.setWorld(worldIn); invalidateNeighbors(); } + @MustBeInvokedByOverriders @Override public void setPos(@NotNull BlockPos posIn) { super.setPos(posIn); invalidateNeighbors(); } + @MustBeInvokedByOverriders @Override public void invalidate() { super.invalidate(); invalidateNeighbors(); } + @MustBeInvokedByOverriders + @Override + public void onChunkUnload() { + super.onChunkUnload(); + invalidateNeighbors(); + } + @Override public @Nullable TileEntity getNeighbor(@NotNull EnumFacing facing) { if (world == null || pos == null) return null; diff --git a/src/main/java/gregtech/api/metatileentity/SimpleGeneratorMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/SimpleGeneratorMetaTileEntity.java index ee42074ecaa..e4443e15cd5 100644 --- a/src/main/java/gregtech/api/metatileentity/SimpleGeneratorMetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/SimpleGeneratorMetaTileEntity.java @@ -103,9 +103,11 @@ protected ModularUI.Builder createGuiTemplate(EntityPlayer player) { yOffset = FONT_HEIGHT; ModularUI.Builder builder; - if (handlesRecipeOutputs) builder = workableRecipeMap.createUITemplate(workable::getProgressPercent, - importItems, exportItems, importFluids, exportFluids, yOffset); - else builder = workableRecipeMap.createUITemplateNoOutputs(workable::getProgressPercent, importItems, + if (handlesRecipeOutputs) + builder = workableRecipeMap.getRecipeMapUI().createUITemplate(workable::getProgressPercent, + importItems, exportItems, importFluids, exportFluids, yOffset); + else builder = workableRecipeMap.getRecipeMapUI().createUITemplateNoOutputs(workable::getProgressPercent, + importItems, exportItems, importFluids, exportFluids, yOffset); builder.widget(new LabelWidget(6, 6, getMetaFullName())) .bindPlayerInventory(player.inventory, GuiTextures.SLOT, yOffset); diff --git a/src/main/java/gregtech/api/metatileentity/SimpleMachineMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/SimpleMachineMetaTileEntity.java index d479ec233f0..4547f5eaac0 100644 --- a/src/main/java/gregtech/api/metatileentity/SimpleMachineMetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/SimpleMachineMetaTileEntity.java @@ -4,12 +4,22 @@ import gregtech.api.capability.GregtechTileCapabilities; import gregtech.api.capability.IActiveOutputSide; import gregtech.api.capability.IGhostSlotConfigurable; -import gregtech.api.capability.impl.*; +import gregtech.api.capability.impl.EnergyContainerHandler; +import gregtech.api.capability.impl.FluidHandlerProxy; +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.capability.impl.GhostCircuitItemStackHandler; +import gregtech.api.capability.impl.ItemHandlerList; +import gregtech.api.capability.impl.ItemHandlerProxy; import gregtech.api.cover.Cover; import gregtech.api.gui.GuiTextures; import gregtech.api.gui.ModularUI; import gregtech.api.gui.resources.TextureArea; -import gregtech.api.gui.widgets.*; +import gregtech.api.gui.widgets.CycleButtonWidget; +import gregtech.api.gui.widgets.GhostCircuitSlotWidget; +import gregtech.api.gui.widgets.ImageWidget; +import gregtech.api.gui.widgets.LabelWidget; +import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.gui.widgets.ToggleButtonWidget; import gregtech.api.items.itemhandlers.GTItemStackHandler; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.recipes.RecipeMap; @@ -478,7 +488,7 @@ protected ModularUI.Builder createGuiTemplate(EntityPlayer player) { yOffset = FONT_HEIGHT; } - ModularUI.Builder builder = workableRecipeMap + ModularUI.Builder builder = workableRecipeMap.getRecipeMapUI() .createUITemplate(workable::getProgressPercent, importItems, exportItems, importFluids, exportFluids, yOffset) .widget(new LabelWidget(5, 5, getMetaFullName())) diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java index a94c78ccdff..55723ef456d 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java @@ -275,7 +275,7 @@ public boolean isMufflerFaceFree() { } /** - * @deprecated Use {@link gregtech.client.particle.VanillaParticleEffects#MUFFLER_SMOKE} instead. + * @deprecated Override {@link #getMufflerParticle()} instead. */ @ApiStatus.ScheduledForRemoval(inVersion = "2.9") @Deprecated @@ -284,6 +284,11 @@ public void runMufflerEffect(float xPos, float yPos, float zPos, float xSpd, flo getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, xPos, yPos, zPos, xSpd, ySpd, zSpd); } + @SideOnly(Side.CLIENT) + public @NotNull EnumParticleTypes getMufflerParticle() { + return EnumParticleTypes.SMOKE_LARGE; + } + /** * Sets the recovery items of this multiblock * diff --git a/src/main/java/gregtech/api/mui/GTGuiTextures.java b/src/main/java/gregtech/api/mui/GTGuiTextures.java new file mode 100644 index 00000000000..d1f432c6c07 --- /dev/null +++ b/src/main/java/gregtech/api/mui/GTGuiTextures.java @@ -0,0 +1,465 @@ +package gregtech.api.mui; + +import gregtech.api.GTValues; + +import com.cleanroommc.modularui.drawable.UITexture; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * GT MUI textures.
+ * Marked experimental as some of these textures may disappear or be renamed at some point + * while MUI port is still ongoing. When MUI port is done, this annotation will be removed. + */ +// TODO ^ +@ApiStatus.Experimental +public class GTGuiTextures { + + /** Keys used for GT assets registered for use in Themes */ + public static class IDs { + + public static final String STANDARD_BACKGROUND = "gregtech_standard_bg"; + public static final String BRONZE_BACKGROUND = "gregtech_bronze_bg"; + public static final String STEEL_BACKGROUND = "gregtech_steel_bg"; + public static final String PRIMITIVE_BACKGROUND = "gregtech_primitive_bg"; + + public static final String STANDARD_SLOT = "gregtech_standard_slot"; + public static final String BRONZE_SLOT = "gregtech_bronze_slot"; + public static final String STEEL_SLOT = "gregtech_steel_slot"; + public static final String PRIMITIVE_SLOT = "gregtech_primitive_slot"; + + public static final String STANDARD_FLUID_SLOT = "gregtech_standard_fluid_slot"; + + public static final String STANDARD_BUTTON = "gregtech_standard_button"; + } + + // ICONS + /** @apiNote You may want {@link GTGuiTextures#getLogo()} instead. */ + public static final UITexture GREGTECH_LOGO = fullImage("textures/gui/icon/gregtech_logo.png"); + /** @apiNote You may want {@link GTGuiTextures#getLogo()} instead. */ + public static final UITexture GREGTECH_LOGO_XMAS = fullImage("textures/gui/icon/gregtech_logo_xmas.png"); + public static final UITexture GREGTECH_LOGO_DARK = fullImage("textures/gui/icon/gregtech_logo_dark.png"); + // todo blinking GT logos + + public static final UITexture INDICATOR_NO_ENERGY = fullImage("textures/gui/base/indicator_no_energy.png"); + public static final UITexture INDICATOR_NO_STEAM_BRONZE = fullImage( + "textures/gui/base/indicator_no_steam_bronze.png"); + public static final UITexture INDICATOR_NO_STEAM_STEEL = fullImage( + "textures/gui/base/indicator_no_steam_steel.png"); + public static final UITexture TANK_ICON = fullImage("textures/gui/base/tank_icon.png"); + + // BACKGROUNDS + public static final UITexture BACKGROUND = UITexture.builder() + .location(GTValues.MODID, "textures/gui/base/background.png") + .imageSize(176, 166) + .adaptable(3) + .name(IDs.STANDARD_BACKGROUND) + .canApplyTheme() + .build(); + + public static final UITexture BACKGROUND_POPUP = UITexture.builder() + .location(GTValues.MODID, "textures/gui/base/background_popup.png") + .imageSize(195, 136) + .adaptable(4) + .canApplyTheme() + .build(); + + // todo BORDERED/BOXED backgrounds will not be ported, if possible + + public static final UITexture BACKGROUND_BRONZE = UITexture.builder() + .location(GTValues.MODID, "textures/gui/base/background_bronze.png") + .imageSize(176, 166) + .adaptable(3) + .name(IDs.BRONZE_BACKGROUND) + .build(); + + public static final UITexture BACKGROUND_STEEL = UITexture.builder() + .location(GTValues.MODID, "textures/gui/base/background_steel.png") + .imageSize(176, 166) + .adaptable(3) + .name(IDs.STEEL_BACKGROUND) + .build(); + + // todo move to textures/gui/base + public static final UITexture BACKGROUND_PRIMITIVE = UITexture.builder() + .location(GTValues.MODID, "textures/gui/primitive/primitive_background.png") + .imageSize(176, 166) + .adaptable(3) + .name(IDs.PRIMITIVE_BACKGROUND) + .build(); + + // todo clipboard backgrounds, may deserve some redoing + + // DISPLAYS + public static final UITexture DISPLAY = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/display.png") + .imageSize(143, 75) + .adaptable(2) + .canApplyTheme() + .build(); + + public static final UITexture DISPLAY_BRONZE = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/display_bronze.png") + .imageSize(143, 75) + .adaptable(2) + .build(); + + public static final UITexture DISPLAY_STEEL = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/display_steel.png") + .imageSize(143, 75) + .adaptable(2) + .build(); + + // todo primitive display? + + // SLOTS + public static final UITexture SLOT = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/slot.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.STANDARD_SLOT) + .canApplyTheme() + .build(); + + public static final UITexture SLOT_BRONZE = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/slot_bronze.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.BRONZE_SLOT) + .build(); + + public static final UITexture SLOT_STEEL = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/slot_steel.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.STEEL_SLOT) + .build(); + + // todo move to textures/gui/base + public static final UITexture SLOT_PRIMITIVE = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/primitive/primitive_slot.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.PRIMITIVE_SLOT) + .build(); + + public static final UITexture FLUID_SLOT = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/base/fluid_slot.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.STANDARD_FLUID_SLOT) + .canApplyTheme() + .build(); + + // todo bronze/steel/primitive fluid slots? + + // SLOT OVERLAYS + public static final UITexture ATOMIC_OVERLAY_1 = fullImage("textures/gui/overlay/atomic_overlay_1.png", true); + public static final UITexture ATOMIC_OVERLAY_2 = fullImage("textures/gui/overlay/atomic_overlay_2.png", true); + public static final UITexture ARROW_INPUT_OVERLAY = fullImage("textures/gui/overlay/arrow_input_overlay.png", true); + public static final UITexture ARROW_OUTPUT_OVERLAY = fullImage("textures/gui/overlay/arrow_output_overlay.png", + true); + public static final UITexture BATTERY_OVERLAY = fullImage("textures/gui/overlay/battery_overlay.png", true); + public static final UITexture BEAKER_OVERLAY_1 = fullImage("textures/gui/overlay/beaker_overlay_1.png", true); + public static final UITexture BEAKER_OVERLAY_2 = fullImage("textures/gui/overlay/beaker_overlay_2.png", true); + public static final UITexture BEAKER_OVERLAY_3 = fullImage("textures/gui/overlay/beaker_overlay_3.png", true); + public static final UITexture BEAKER_OVERLAY_4 = fullImage("textures/gui/overlay/beaker_overlay_4.png", true); + public static final UITexture BENDER_OVERLAY = fullImage("textures/gui/overlay/bender_overlay.png", true); + public static final UITexture BOX_OVERLAY = fullImage("textures/gui/overlay/box_overlay.png", true); + public static final UITexture BOXED_OVERLAY = fullImage("textures/gui/overlay/boxed_overlay.png", true); + public static final UITexture BREWER_OVERLAY = fullImage("textures/gui/overlay/brewer_overlay.png", true); + public static final UITexture CANNER_OVERLAY = fullImage("textures/gui/overlay/canner_overlay.png", true); + public static final UITexture CHARGER_OVERLAY = fullImage("textures/gui/overlay/charger_slot_overlay.png", true); + public static final UITexture CANISTER_OVERLAY = fullImage("textures/gui/overlay/canister_overlay.png", true); + public static final UITexture CANISTER_OVERLAY_BRONZE = fullImage( + "textures/gui/overlay/canister_overlay_bronze.png"); + public static final UITexture CANISTER_OVERLAY_STEEL = fullImage("textures/gui/overlay/canister_overlay_steel.png"); + public static final UITexture CENTRIFUGE_OVERLAY = fullImage("textures/gui/overlay/centrifuge_overlay.png", true); + public static final UITexture CIRCUIT_OVERLAY = fullImage("textures/gui/overlay/circuit_overlay.png", true); + public static final UITexture COAL_OVERLAY_BRONZE = fullImage("textures/gui/overlay/coal_overlay_bronze.png"); + public static final UITexture COAL_OVERLAY_STEEL = fullImage("textures/gui/overlay/coal_overlay_steel.png"); + public static final UITexture COMPRESSOR_OVERLAY = fullImage("textures/gui/overlay/compressor_overlay.png", true); + public static final UITexture COMPRESSOR_OVERLAY_BRONZE = fullImage( + "textures/gui/overlay/compressor_overlay_bronze.png"); + public static final UITexture COMPRESSOR_OVERLAY_STEEL = fullImage( + "textures/gui/overlay/compressor_overlay_steel.png"); + public static final UITexture CRACKING_OVERLAY_1 = fullImage("textures/gui/overlay/cracking_overlay_1.png", true); + public static final UITexture CRACKING_OVERLAY_2 = fullImage("textures/gui/overlay/cracking_overlay_2.png", true); + public static final UITexture CRUSHED_ORE_OVERLAY = fullImage("textures/gui/overlay/crushed_ore_overlay.png", true); + public static final UITexture CRUSHED_ORE_OVERLAY_BRONZE = fullImage( + "textures/gui/overlay/crushed_ore_overlay_bronze.png"); + public static final UITexture CRUSHED_ORE_OVERLAY_STEEL = fullImage( + "textures/gui/overlay/crushed_ore_overlay_steel.png"); + public static final UITexture CRYSTAL_OVERLAY = fullImage("textures/gui/overlay/crystal_overlay.png", true); + public static final UITexture CUTTER_OVERLAY = fullImage("textures/gui/overlay/cutter_overlay.png", true); + public static final UITexture DARK_CANISTER_OVERLAY = fullImage("textures/gui/overlay/dark_canister_overlay.png", + true); + public static final UITexture DUST_OVERLAY = fullImage("textures/gui/overlay/dust_overlay.png", true); + public static final UITexture DUST_OVERLAY_BRONZE = fullImage("textures/gui/overlay/dust_overlay_bronze.png"); + public static final UITexture DUST_OVERLAY_STEEL = fullImage("textures/gui/overlay/dust_overlay_steel.png"); + public static final UITexture EXTRACTOR_OVERLAY = fullImage("textures/gui/overlay/extractor_overlay.png", true); + public static final UITexture EXTRACTOR_OVERLAY_BRONZE = fullImage( + "textures/gui/overlay/extractor_overlay_bronze.png"); + public static final UITexture EXTRACTOR_OVERLAY_STEEL = fullImage( + "textures/gui/overlay/extractor_overlay_steel.png"); + public static final UITexture FILTER_SLOT_OVERLAY = fullImage("textures/gui/overlay/filter_slot_overlay.png", true); + public static final UITexture FURNACE_OVERLAY_1 = fullImage("textures/gui/overlay/furnace_overlay_1.png", true); + public static final UITexture FURNACE_OVERLAY_2 = fullImage("textures/gui/overlay/furnace_overlay_2.png", true); + public static final UITexture FURNACE_OVERLAY_BRONZE = fullImage("textures/gui/overlay/furnace_overlay_bronze.png"); + public static final UITexture FURNACE_OVERLAY_STEEL = fullImage("textures/gui/overlay/furnace_overlay_steel.png"); + public static final UITexture HAMMER_OVERLAY = fullImage("textures/gui/overlay/hammer_overlay.png", true); + public static final UITexture HAMMER_OVERLAY_BRONZE = fullImage("textures/gui/overlay/hammer_overlay_bronze.png"); + public static final UITexture HAMMER_OVERLAY_STEEL = fullImage("textures/gui/overlay/hammer_overlay_steel.png"); + public static final UITexture HEATING_OVERLAY_1 = fullImage("textures/gui/overlay/heating_overlay_1.png", true); + public static final UITexture HEATING_OVERLAY_2 = fullImage("textures/gui/overlay/heating_overlay_2.png", true); + public static final UITexture IMPLOSION_OVERLAY_1 = fullImage("textures/gui/overlay/implosion_overlay_1.png", true); + public static final UITexture IMPLOSION_OVERLAY_2 = fullImage("textures/gui/overlay/implosion_overlay_2.png", true); + public static final UITexture IN_SLOT_OVERLAY = fullImage("textures/gui/overlay/in_slot_overlay.png", true); + public static final UITexture IN_SLOT_OVERLAY_BRONZE = fullImage("textures/gui/overlay/in_slot_overlay_bronze.png"); + public static final UITexture IN_SLOT_OVERLAY_STEEL = fullImage("textures/gui/overlay/in_slot_overlay_steel.png"); + public static final UITexture INGOT_OVERLAY = fullImage("textures/gui/overlay/ingot_overlay.png", true); + public static final UITexture INT_CIRCUIT_OVERLAY = fullImage("textures/gui/overlay/int_circuit_overlay.png", true); + public static final UITexture LENS_OVERLAY = fullImage("textures/gui/overlay/lens_overlay.png", true); + public static final UITexture LIGHTNING_OVERLAY_1 = fullImage("textures/gui/overlay/lightning_overlay_1.png", true); + public static final UITexture LIGHTNING_OVERLAY_2 = fullImage("textures/gui/overlay/lightning_overlay_2.png", true); + public static final UITexture MOLD_OVERLAY = fullImage("textures/gui/overlay/mold_overlay.png", true); + public static final UITexture MOLECULAR_OVERLAY_1 = fullImage("textures/gui/overlay/molecular_overlay_1.png", true); + public static final UITexture MOLECULAR_OVERLAY_2 = fullImage("textures/gui/overlay/molecular_overlay_2.png", true); + public static final UITexture MOLECULAR_OVERLAY_3 = fullImage("textures/gui/overlay/molecular_overlay_3.png", true); + public static final UITexture MOLECULAR_OVERLAY_4 = fullImage("textures/gui/overlay/molecular_overlay_4.png", true); + public static final UITexture OUT_SLOT_OVERLAY = fullImage("textures/gui/overlay/out_slot_overlay.png", true); + public static final UITexture OUT_SLOT_OVERLAY_BRONZE = fullImage( + "textures/gui/overlay/out_slot_overlay_bronze.png"); + public static final UITexture OUT_SLOT_OVERLAY_STEEL = fullImage("textures/gui/overlay/out_slot_overlay_steel.png"); + public static final UITexture PAPER_OVERLAY = fullImage("textures/gui/overlay/paper_overlay.png", true); + public static final UITexture PRINTED_PAPER_OVERLAY = fullImage("textures/gui/overlay/printed_paper_overlay.png", + true); + public static final UITexture PIPE_OVERLAY_2 = fullImage("textures/gui/overlay/pipe_overlay_2.png", true); + public static final UITexture PIPE_OVERLAY_1 = fullImage("textures/gui/overlay/pipe_overlay_1.png", true); + public static final UITexture PRESS_OVERLAY_1 = fullImage("textures/gui/overlay/press_overlay_1.png", true); + public static final UITexture PRESS_OVERLAY_2 = fullImage("textures/gui/overlay/press_overlay_2.png", true); + public static final UITexture PRESS_OVERLAY_3 = fullImage("textures/gui/overlay/press_overlay_3.png", true); + public static final UITexture PRESS_OVERLAY_4 = fullImage("textures/gui/overlay/press_overlay_4.png", true); + public static final UITexture SAWBLADE_OVERLAY = fullImage("textures/gui/overlay/sawblade_overlay.png", true); + public static final UITexture SOLIDIFIER_OVERLAY = fullImage("textures/gui/overlay/solidifier_overlay.png", true); + public static final UITexture STRING_SLOT_OVERLAY = fullImage("textures/gui/overlay/string_slot_overlay.png", true); + public static final UITexture TOOL_SLOT_OVERLAY = fullImage("textures/gui/overlay/tool_slot_overlay.png", true); + public static final UITexture TURBINE_OVERLAY = fullImage("textures/gui/overlay/turbine_overlay.png", true); + public static final UITexture VIAL_OVERLAY_1 = fullImage("textures/gui/overlay/vial_overlay_1.png", true); + public static final UITexture VIAL_OVERLAY_2 = fullImage("textures/gui/overlay/vial_overlay_2.png", true); + public static final UITexture WIREMILL_OVERLAY = fullImage("textures/gui/overlay/wiremill_overlay.png", true); + public static final UITexture POSITIVE_MATTER_OVERLAY = fullImage( + "textures/gui/overlay/positive_matter_overlay.png", true); + public static final UITexture NEUTRAL_MATTER_OVERLAY = fullImage("textures/gui/overlay/neutral_matter_overlay.png", + true); + public static final UITexture DATA_ORB_OVERLAY = fullImage("textures/gui/overlay/data_orb_overlay.png", true); + public static final UITexture SCANNER_OVERLAY = fullImage("textures/gui/overlay/scanner_overlay.png", true); + public static final UITexture DUCT_TAPE_OVERLAY = fullImage("textures/gui/overlay/duct_tape_overlay.png", true); + public static final UITexture RESEARCH_STATION_OVERLAY = fullImage( + "textures/gui/overlay/research_station_overlay.png", true); + + // BUTTONS + + public static final UITexture BUTTON = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/widget/button.png") + .imageSize(18, 18) + .adaptable(1) + .name(IDs.STANDARD_BUTTON) + .canApplyTheme() + .build(); + + // BUTTON OVERLAYS + + public static final UITexture BUTTON_ITEM_OUTPUT = fullImage("textures/gui/widget/button_item_output_overlay.png"); + public static final UITexture BUTTON_FLUID_OUTPUT = fullImage( + "textures/gui/widget/button_fluid_output_overlay.png"); + public static final UITexture BUTTON_AUTO_COLLAPSE = fullImage( + "textures/gui/widget/button_auto_collapse_overlay.png"); + public static final UITexture BUTTON_X = fullImage("textures/gui/widget/button_x_overlay.png", true); + + // PROGRESS BARS + public static final UITexture PROGRESS_BAR_ARC_FURNACE = progressBar( + "textures/gui/progress_bar/progress_bar_arc_furnace.png", true); + public static final UITexture PROGRESS_BAR_ARROW = progressBar("textures/gui/progress_bar/progress_bar_arrow.png", + true); + public static final UITexture PROGRESS_BAR_ARROW_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_arrow_bronze.png"); + public static final UITexture PROGRESS_BAR_ARROW_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_arrow_steel.png"); + public static final UITexture PROGRESS_BAR_ARROW_MULTIPLE = progressBar( + "textures/gui/progress_bar/progress_bar_arrow_multiple.png", true); + public static final UITexture PROGRESS_BAR_BATH = progressBar("textures/gui/progress_bar/progress_bar_bath.png", + true); + public static final UITexture PROGRESS_BAR_BENDING = progressBar( + "textures/gui/progress_bar/progress_bar_bending.png", true); + public static final UITexture PROGRESS_BAR_CANNER = progressBar("textures/gui/progress_bar/progress_bar_canner.png", + true); + public static final UITexture PROGRESS_BAR_CIRCUIT = progressBar( + "textures/gui/progress_bar/progress_bar_circuit.png", true); + public static final UITexture PROGRESS_BAR_CIRCUIT_ASSEMBLER = progressBar( + "textures/gui/progress_bar/progress_bar_circuit_assembler.png", true); + public static final UITexture PROGRESS_BAR_COMPRESS = progressBar( + "textures/gui/progress_bar/progress_bar_compress.png", true); + public static final UITexture PROGRESS_BAR_COMPRESS_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_compress_bronze.png"); + public static final UITexture PROGRESS_BAR_COMPRESS_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_compress_steel.png"); + public static final UITexture PROGRESS_BAR_CRACKING = progressBar( + "textures/gui/progress_bar/progress_bar_cracking.png", true); + public static final UITexture PROGRESS_BAR_CRACKING_INPUT = progressBar( + "textures/gui/progress_bar/progress_bar_cracking_2.png", 21, 38, true); + public static final UITexture PROGRESS_BAR_CRYSTALLIZATION = progressBar( + "textures/gui/progress_bar/progress_bar_crystallization.png", true); + public static final UITexture PROGRESS_BAR_EXTRACT = progressBar( + "textures/gui/progress_bar/progress_bar_extract.png", true); + public static final UITexture PROGRESS_BAR_EXTRACT_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_extract_bronze.png"); + public static final UITexture PROGRESS_BAR_EXTRACT_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_extract_steel.png"); + public static final UITexture PROGRESS_BAR_EXTRUDER = progressBar( + "textures/gui/progress_bar/progress_bar_extruder.png", true); + public static final UITexture PROGRESS_BAR_FUSION = progressBar("textures/gui/progress_bar/progress_bar_fusion.png", + true); + public static final UITexture PROGRESS_BAR_GAS_COLLECTOR = progressBar( + "textures/gui/progress_bar/progress_bar_gas_collector.png", true); + public static final UITexture PROGRESS_BAR_HAMMER = progressBar("textures/gui/progress_bar/progress_bar_hammer.png", + true); + public static final UITexture PROGRESS_BAR_HAMMER_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_hammer_bronze.png"); + public static final UITexture PROGRESS_BAR_HAMMER_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_hammer_steel.png"); + public static final UITexture PROGRESS_BAR_HAMMER_BASE = fullImage( + "textures/gui/progress_bar/progress_bar_hammer_base.png", true); + public static final UITexture PROGRESS_BAR_HAMMER_BASE_BRONZE = fullImage( + "textures/gui/progress_bar/progress_bar_hammer_base_bronze.png"); + public static final UITexture PROGRESS_BAR_HAMMER_BASE_STEEL = fullImage( + "textures/gui/progress_bar/progress_bar_hammer_base_steel.png"); + public static final UITexture PROGRESS_BAR_LATHE = progressBar("textures/gui/progress_bar/progress_bar_lathe.png", + true); + public static final UITexture PROGRESS_BAR_LATHE_BASE = fullImage( + "textures/gui/progress_bar/progress_bar_lathe_base.png", true); + public static final UITexture PROGRESS_BAR_MACERATE = progressBar( + "textures/gui/progress_bar/progress_bar_macerate.png", true); + public static final UITexture PROGRESS_BAR_MACERATE_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_macerate_bronze.png"); + public static final UITexture PROGRESS_BAR_MACERATE_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_macerate_steel.png"); + public static final UITexture PROGRESS_BAR_MAGNET = progressBar("textures/gui/progress_bar/progress_bar_magnet.png", + true); + public static final UITexture PROGRESS_BAR_MASS_FAB = progressBar( + "textures/gui/progress_bar/progress_bar_mass_fab.png", true); + public static final UITexture PROGRESS_BAR_MIXER = progressBar("textures/gui/progress_bar/progress_bar_mixer.png", + true); + public static final UITexture PROGRESS_BAR_PACKER = progressBar("textures/gui/progress_bar/progress_bar_packer.png", + true); + public static final UITexture PROGRESS_BAR_RECYCLER = progressBar( + "textures/gui/progress_bar/progress_bar_recycler.png", true); + public static final UITexture PROGRESS_BAR_REPLICATOR = progressBar( + "textures/gui/progress_bar/progress_bar_replicator.png", true); + public static final UITexture PROGRESS_BAR_SIFT = progressBar("textures/gui/progress_bar/progress_bar_sift.png", + true); + public static final UITexture PROGRESS_BAR_SLICE = progressBar("textures/gui/progress_bar/progress_bar_slice.png", + true); + public static final UITexture PROGRESS_BAR_UNPACKER = progressBar( + "textures/gui/progress_bar/progress_bar_unpacker.png", true); + public static final UITexture PROGRESS_BAR_WIREMILL = progressBar( + "textures/gui/progress_bar/progress_bar_wiremill.png", true); + + // more custom progress bars + // todo these boiler empty bars can probably be replaced by using a resized steam slot texture + public static final UITexture PROGRESS_BAR_BOILER_EMPTY_BRONZE = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/progress_bar/progress_bar_boiler_empty_bronze.png") + .imageSize(10, 54) + .adaptable(1) + .build(); + public static final UITexture PROGRESS_BAR_BOILER_EMPTY_STEEL = new UITexture.Builder() + .location(GTValues.MODID, "textures/gui/progress_bar/progress_bar_boiler_empty_steel.png") + .imageSize(10, 54) + .adaptable(1) + .build(); + public static final UITexture PROGRESS_BAR_BOILER_FUEL_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_boiler_fuel_bronze.png", 18, 36); + public static final UITexture PROGRESS_BAR_BOILER_FUEL_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_boiler_fuel_steel.png", 18, 36); + public static final UITexture PROGRESS_BAR_BOILER_HEAT = progressBar( + "textures/gui/progress_bar/progress_bar_boiler_heat.png", true); + public static final UITexture PROGRESS_BAR_ASSEMBLY_LINE = progressBar( + "textures/gui/progress_bar/progress_bar_assembly_line.png", 54, 144, true); + public static final UITexture PROGRESS_BAR_ASSEMBLY_LINE_ARROW = progressBar( + "textures/gui/progress_bar/progress_bar_assembly_line_arrow.png", 10, 36, true); + public static final UITexture PROGRESS_BAR_COKE_OVEN = progressBar( + "textures/gui/progress_bar/progress_bar_coke_oven.png", 36, 36, true); + public static final UITexture PROGRESS_BAR_DISTILLATION_TOWER = progressBar( + "textures/gui/progress_bar/progress_bar_distillation_tower.png", 66, 116, true); + public static final UITexture PROGRESS_BAR_SOLAR_BRONZE = progressBar( + "textures/gui/progress_bar/progress_bar_solar_bronze.png", 10, 20); + public static final UITexture PROGRESS_BAR_SOLAR_STEEL = progressBar( + "textures/gui/progress_bar/progress_bar_solar_steel.png", 10, 20); + public static final UITexture PROGRESS_BAR_RESEARCH_STATION_1 = progressBar( + "textures/gui/progress_bar/progress_bar_research_station_1.png", 54, 10, true); + public static final UITexture PROGRESS_BAR_RESEARCH_STATION_2 = progressBar( + "textures/gui/progress_bar/progress_bar_research_station_2.png", 10, 36, true); + public static final UITexture PROGRESS_BAR_RESEARCH_STATION_BASE = fullImage( + "textures/gui/progress_bar/progress_bar_research_station_base.png", true); + public static final UITexture PROGRESS_BAR_FUSION_ENERGY = progressBar( + "textures/gui/progress_bar/progress_bar_fusion_energy.png", 94, 14); + public static final UITexture PROGRESS_BAR_FUSION_HEAT = progressBar( + "textures/gui/progress_bar/progress_bar_fusion_heat.png", 94, 14); + public static final UITexture PROGRESS_BAR_MULTI_ENERGY_YELLOW = progressBar( + "textures/gui/progress_bar/progress_bar_multi_energy_yellow.png", 190, 14); + public static final UITexture PROGRESS_BAR_HPCA_COMPUTATION = progressBar( + "textures/gui/progress_bar/progress_bar_hpca_computation.png", 94, 14); + public static final UITexture PROGRESS_BAR_LCE_FUEL = progressBar( + "textures/gui/progress_bar/progress_bar_lce_fuel.png", 62, 14); + public static final UITexture PROGRESS_BAR_LCE_LUBRICANT = progressBar( + "textures/gui/progress_bar/progress_bar_lce_lubricant.png", 62, 14); + public static final UITexture PROGRESS_BAR_LCE_OXYGEN = progressBar( + "textures/gui/progress_bar/progress_bar_lce_oxygen.png", 62, 14); + public static final UITexture PROGRESS_BAR_TURBINE_ROTOR_SPEED = progressBar( + "textures/gui/progress_bar/progress_bar_turbine_rotor_speed.png", 62, 14); + public static final UITexture PROGRESS_BAR_TURBINE_ROTOR_DURABILITY = progressBar( + "textures/gui/progress_bar/progress_bar_turbine_rotor_durability.png", 62, 14); + public static final UITexture PROGRESS_BAR_FLUID_RIG_DEPLETION = progressBar( + "textures/gui/progress_bar/progress_bar_fluid_rig_depletion.png", 190, 14); + + // MISC + + public static void init() {/**/} + + private static UITexture fullImage(String path) { + return fullImage(path, false); + } + + private static UITexture fullImage(String path, boolean canApplyTheme) { + return UITexture.fullImage(GTValues.MODID, path, canApplyTheme); + } + + private static UITexture progressBar(String path) { + return progressBar(path, 20, 40, false); + } + + private static UITexture progressBar(String path, boolean canApplyTheme) { + return progressBar(path, 20, 40, canApplyTheme); + } + + private static UITexture progressBar(String path, int width, int height) { + return progressBar(path, width, height, false); + } + + private static UITexture progressBar(String path, int width, int height, boolean canApplyTheme) { + UITexture.Builder builder = new UITexture.Builder() + .location(GTValues.MODID, path) + .imageSize(width, height); + if (canApplyTheme) builder.canApplyTheme(); + return builder.build(); + } + + // todo steam logos? multi indicator blinking logos? + public static @NotNull UITexture getLogo(GTGuiTheme theme) { + if (theme != null) { + UITexture logo = theme.getLogo(); + if (logo != null) return logo; + } + return GTValues.XMAS.get() ? GREGTECH_LOGO_XMAS : GREGTECH_LOGO; + } +} diff --git a/src/main/java/gregtech/api/mui/GTGuiTheme.java b/src/main/java/gregtech/api/mui/GTGuiTheme.java new file mode 100644 index 00000000000..c3f80b09943 --- /dev/null +++ b/src/main/java/gregtech/api/mui/GTGuiTheme.java @@ -0,0 +1,387 @@ +package gregtech.api.mui; + +import gregtech.common.ConfigHolder; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.drawable.UITexture; +import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.theme.ReloadThemeEvent; +import com.cleanroommc.modularui.utils.JsonBuilder; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class GTGuiTheme { + + private static final List THEMES = new ArrayList<>(); + + public static final GTGuiTheme STANDARD = templateBuilder("gregtech_standard") + .panel(GTGuiTextures.IDs.STANDARD_BACKGROUND) + .itemSlot(GTGuiTextures.IDs.STANDARD_SLOT) + .fluidSlot(GTGuiTextures.IDs.STANDARD_FLUID_SLOT) + .color(ConfigHolder.client.defaultUIColor) + .button(GTGuiTextures.IDs.STANDARD_BUTTON) + .simpleToggleButton(GTGuiTextures.IDs.STANDARD_BUTTON, + GTGuiTextures.IDs.STANDARD_SLOT, + ConfigHolder.client.defaultUIColor) + .build(); + + // TODO Cover theme to utilize the GT5u-like button textures vs the standard ones + + public static final GTGuiTheme BRONZE = templateBuilder("gregtech_bronze") + .panel(GTGuiTextures.IDs.BRONZE_BACKGROUND) + .itemSlot(GTGuiTextures.IDs.BRONZE_SLOT) + .build(); + + public static final GTGuiTheme STEEL = templateBuilder("gregtech_steel") + .panel(GTGuiTextures.IDs.STEEL_BACKGROUND) + .itemSlot(GTGuiTextures.IDs.STEEL_SLOT) + .build(); + + public static final GTGuiTheme PRIMITIVE = templateBuilder("gregtech_primitive") + .panel(GTGuiTextures.IDs.PRIMITIVE_BACKGROUND) + .itemSlot(GTGuiTextures.IDs.PRIMITIVE_SLOT) + .build(); + + private final String themeId; + + private final List> elementBuilder; + private final JsonBuilder jsonBuilder; + + private Supplier logo; + + private GTGuiTheme(String themeId) { + this.themeId = themeId; + this.jsonBuilder = new JsonBuilder(); + this.elementBuilder = new ArrayList<>(); + THEMES.add(this); + } + + public String getId() { + return themeId; + } + + public ITheme getMuiTheme() { + return IThemeApi.get().getTheme(themeId); + } + + public @Nullable UITexture getLogo() { + if (logo == null) return null; + return logo.get(); + } + + private void register() { + buildJson(); + IThemeApi.get().registerTheme(themeId, jsonBuilder); + } + + private void buildJson() { + elementBuilder.forEach(c -> c.accept(jsonBuilder)); + } + + public static void registerThemes() { + MinecraftForge.EVENT_BUS.register(GTGuiTheme.class); + THEMES.forEach(GTGuiTheme::register); + } + + @SubscribeEvent + public static void onReloadThemes(ReloadThemeEvent.Pre event) { + THEMES.forEach(GTGuiTheme::buildJson); + } + + public static Builder templateBuilder(String themeId) { + Builder builder = new Builder(themeId); + builder.openCloseAnimation(0); + builder.tooltipPos(Tooltip.Pos.NEXT_TO_MOUSE); + builder.smoothProgressBar(true); + return builder; + } + + public static class Builder { + + private final GTGuiTheme theme; + + public Builder(String themeId) { + theme = new GTGuiTheme(themeId); + } + + /** + * Set a parent theme for this theme, which unset values will inherit from. + * If not set, it will use the default theme as the parent (VANILLA). + */ + public Builder parent(String parentId) { + theme.elementBuilder.add(b -> b.add("parent", parentId)); + return this; + } + + /** + * Set a background fallback for when specific widgets do not set their own. + */ + public Builder globalBackground(String backgroundId) { + theme.elementBuilder.add(b -> b.add("background", backgroundId)); + return this; + } + + /** + * Set a tooltip hover background fallback for when specific widgets do not set their own. + */ + public Builder globalHoverBackground(String hoverBackgroundId) { + theme.elementBuilder.add(b -> b.add("hoverBackground", hoverBackgroundId)); + return this; + } + + /** + * Set the window open/close animation speed. Overrides global cfg. + * + * @param rate the rate in frames to play the open/close animation over, or 0 for no animation + */ + public Builder openCloseAnimation(int rate) { + theme.elementBuilder.add(b -> b.add("openCloseAnimation", rate)); + return this; + } + + /** Set whether progress bars should animate smoothly. Overrides global cfg. */ + public Builder smoothProgressBar(boolean smoothBar) { + theme.elementBuilder.add(b -> b.add("smoothProgressBar", smoothBar)); + return this; + } + + /** Set the tooltip pos for this theme. Overrides global cfg. */ + public Builder tooltipPos(Tooltip.Pos tooltipPos) { + theme.elementBuilder.add(b -> b.add("tooltipPos", tooltipPos.name())); + return this; + } + + /** Set a global UI coloration for this theme. */ + public Builder color(int color) { + theme.elementBuilder.add(b -> b.add("color", color)); + return this; + } + + /** Set a global UI text coloration for this theme. */ + public Builder textColor(int textColor) { + theme.elementBuilder.add(b -> b.add("textColor", textColor)); + return this; + } + + /** Enable text shadow for the global UI text for this theme. */ + public Builder textShadow() { + theme.elementBuilder.add(b -> b.add("textShadow", true)); + return this; + } + + /** + * Set a custom panel (background texture) for UIs with this theme. + */ + public Builder panel(String panelId) { + theme.elementBuilder.add(b -> b + .add("panel", new JsonBuilder() + .add("background", new JsonBuilder() + .add("type", "texture") + .add("id", panelId)))); + return this; + } + + /** + * Set a custom button texture for UIs with this theme. + */ + public Builder button(String buttonId) { + return button(buttonId, buttonId, 0xFFFFFFFF, false); + } + + /** + * Set a custom button texture for UIs with this theme. + * + * @param buttonId The ID of the button texture + * @param hoverId The ID of the button texture while hovering over the button with your mouse + */ + public Builder button(String buttonId, String hoverId) { + return button(buttonId, hoverId, 0xFFFFFFFF, false); + } + + /** + * Set a custom button texture for UIs with this theme. + * + * @param buttonId The ID of the button texture + * @param hoverId The ID of the button texture while hovering over the button with your mouse + * @param textColor The color of text overlaid on this button + * @param textShadow If text overlaid on this button should have a text shadow + */ + public Builder button(String buttonId, String hoverId, int textColor, boolean textShadow) { + theme.elementBuilder.add(b -> b + .add("button", new JsonBuilder() + .add("background", new JsonBuilder() + .add("type", "texture") + .add("id", buttonId)) + .add("hoverBackground", hoverId) + .add("textColor", textColor) + .add("textShadow", textShadow))); + return this; + } + + /** + * Set a custom item slot texture for UIs with this theme. + */ + public Builder itemSlot(String itemSlotId) { + return itemSlot(itemSlotId, 0x60FFFFFF); + } + + /** + * Set a custom item slot texture for UIs with this theme. + * + * @param itemSlotId The ID of the item slot texture + * @param hoverColor The color of the tooltip hover box for this widget + */ + public Builder itemSlot(String itemSlotId, int hoverColor) { + theme.elementBuilder.add(b -> b + .add("itemSlot", new JsonBuilder() + .add("background", new JsonBuilder() + .add("type", "texture") + .add("id", itemSlotId)) + .add("slotHoverColor", hoverColor))); + return this; + } + + /** + * Set a custom fluid slot texture for UIs with this theme. + */ + public Builder fluidSlot(String fluidSlotId) { + return fluidSlot(fluidSlotId, 0x60FFFFFF); + } + + /** + * Set a custom fluid slot texture for UIs with this theme. + * + * @param fluidSlotId The ID of the fluid slot texture + * @param hoverColor The color of the tooltip hover box for this widget + */ + public Builder fluidSlot(String fluidSlotId, int hoverColor) { + theme.elementBuilder.add(b -> b + .add("fluidSlot", new JsonBuilder() + .add("background", new JsonBuilder() + .add("type", "texture") + .add("id", fluidSlotId)) + .add("slotHoverColor", hoverColor))); + return this; + } + + /** + * Set the text color for text fields in UIs with this theme. + */ + public Builder textField(int textColor) { + return textField(textColor, 0xFF2F72A8); + } + + /** + * Set the text color for text fields in UIs with this theme. + * + * @param textColor Text color + * @param markedColor Color of the highlight on selected text + */ + public Builder textField(int textColor, int markedColor) { + theme.elementBuilder.add(b -> b + .add("textField", new JsonBuilder() + .add("textColor", textColor) + .add("markedColor", markedColor))); + return this; + } + + /** + * Set the theme options for a ToggleButton widget. + * + * @param backgroundId The main background for the unpressed button + * @param hoverBackgroundId The on-hover background for the unpressed button + * @param selectedBackgroundId The main background for the pressed button + * @param selectedHoverBackgroundId The on-hover background for the pressed button + * @param selectedColor The color to apply to the pressed button + */ + public Builder toggleButton(String backgroundId, String hoverBackgroundId, + String selectedBackgroundId, String selectedHoverBackgroundId, int selectedColor) { + return toggleButton( + backgroundId, hoverBackgroundId, + selectedBackgroundId, selectedHoverBackgroundId, + selectedColor, 0xFFBBBBBB, false); + } + + /** + * Set the theme options for a ToggleButton widget. + * + * @param backgroundId The main background for the unpressed button + * @param hoverBackgroundId The on-hover background for the unpressed button + * @param selectedBackgroundId The main background for the pressed button + * @param selectedHoverBackgroundId The on-hover background for the pressed button + * @param selectedColor The color to apply to the pressed button + * @param textColor The color for text overlaid on this button + * @param textShadow Whether to apply text shadow to text overlaid on this button + */ + public Builder toggleButton(String backgroundId, String hoverBackgroundId, + String selectedBackgroundId, String selectedHoverBackgroundId, + int selectedColor, int textColor, boolean textShadow) { + theme.elementBuilder.add(b -> b + .add("toggleButton", new JsonBuilder() + .add("background", new JsonBuilder() + .add("type", "texture") + .add("id", backgroundId)) + .add("hoverBackground", new JsonBuilder() + .add("type", "texture") + .add("id", hoverBackgroundId)) + .add("selectedBackground", new JsonBuilder() + .add("type", "texture") + .add("id", selectedBackgroundId)) + .add("selectedHoverBackground", new JsonBuilder() + .add("type", "texture") + .add("id", selectedHoverBackgroundId)) + .add("selectedColor", selectedColor) + .add("textColor", textColor) + .add("textShadow", textShadow))); + return this; + } + + /** + * Simple toggle button configuration for when you want a button with no texture changes on hover. + * + * @param backgroundId The unselected background texture + * @param selectedBackgroundId The selected background texture + * @param selectedColor The background color when the button is selected + */ + public Builder simpleToggleButton(String backgroundId, String selectedBackgroundId, int selectedColor) { + return simpleToggleButton(backgroundId, selectedBackgroundId, selectedColor, 0xFFBBBBBB, false); + } + + /** + * Simple toggle button configuration for when you want a button with no texture changes on hover. + * + * @param backgroundId The unselected background texture + * @param selectedBackgroundId The selected background texture + * @param selectedColor The background color when the button is selected + * @param textColor The color for text overlaid on this button + * @param textShadow Whether to apply text shadow to text overlaid on this button + */ + public Builder simpleToggleButton(String backgroundId, String selectedBackgroundId, int selectedColor, + int textColor, boolean textShadow) { + return toggleButton( + backgroundId, backgroundId, + selectedBackgroundId, selectedBackgroundId, + selectedColor, textColor, textShadow); + } + + /** + * Set a logo supplier for this theme. + */ + public Builder logo(Supplier logo) { + theme.logo = logo; + return this; + } + + public GTGuiTheme build() { + return theme; + } + } +} diff --git a/src/main/java/gregtech/api/mui/GTGuis.java b/src/main/java/gregtech/api/mui/GTGuis.java new file mode 100644 index 00000000000..d60b66088c7 --- /dev/null +++ b/src/main/java/gregtech/api/mui/GTGuis.java @@ -0,0 +1,82 @@ +package gregtech.api.mui; + +import gregtech.api.cover.Cover; +import gregtech.api.items.metaitem.MetaItem; +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.mui.factory.CoverGuiFactory; +import gregtech.api.mui.factory.MetaItemGuiFactory; +import gregtech.api.mui.factory.MetaTileEntityGuiFactory; + +import net.minecraft.item.ItemStack; + +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +public class GTGuis { + + @ApiStatus.Internal + public static void registerFactories() { + GuiManager.registerFactory(MetaTileEntityGuiFactory.INSTANCE); + GuiManager.registerFactory(MetaItemGuiFactory.INSTANCE); + GuiManager.registerFactory(CoverGuiFactory.INSTANCE); + } + + public static ModularPanel createPanel(String name, int width, int height) { + return ModularPanel.defaultPanel(name, width, height); + } + + public static ModularPanel createPanel(MetaTileEntity mte, int width, int height) { + return createPanel(mte.metaTileEntityId.getPath(), width, height); + } + + public static ModularPanel createPanel(Cover cover, int width, int height) { + return createPanel(cover.getDefinition().getResourceLocation().getPath(), width, height); + } + + public static ModularPanel createPanel(ItemStack stack, int width, int height) { + MetaItem.MetaValueItem valueItem = ((MetaItem) stack.getItem()).getItem(stack); + if (valueItem == null) throw new IllegalArgumentException("Item must be a meta item!"); + return createPanel(valueItem.unlocalizedName, width, height); + } + + public static ModularPanel createPopupPanel(String name, int width, int height) { + return createPopupPanel(name, width, height, false, false); + } + + public static ModularPanel createPopupPanel(String name, int width, int height, boolean disableBelow, + boolean closeOnOutsideClick) { + return new PopupPanel(name, width, height, disableBelow, closeOnOutsideClick); + } + + private static class PopupPanel extends ModularPanel { + + private final boolean disableBelow; + private final boolean closeOnOutsideClick; + + public PopupPanel(@NotNull String name, int width, int height, boolean disableBelow, + boolean closeOnOutsideClick) { + super(name); + flex().startDefaultMode(); + flex().size(width, height).align(Alignment.Center); + flex().endDefaultMode(); + background(GTGuiTextures.BACKGROUND_POPUP); + child(ButtonWidget.panelCloseButton().top(5).right(5)); + this.disableBelow = disableBelow; + this.closeOnOutsideClick = closeOnOutsideClick; + } + + @Override + public boolean disablePanelsBelow() { + return disableBelow; + } + + @Override + public boolean closeOnOutOfBoundsClick() { + return closeOnOutsideClick; + } + } +} diff --git a/src/main/java/gregtech/api/mui/GregTechGuiScreen.java b/src/main/java/gregtech/api/mui/GregTechGuiScreen.java new file mode 100644 index 00000000000..ad5c48449c5 --- /dev/null +++ b/src/main/java/gregtech/api/mui/GregTechGuiScreen.java @@ -0,0 +1,30 @@ +package gregtech.api.mui; + +import gregtech.api.GTValues; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; + +@SideOnly(Side.CLIENT) +public class GregTechGuiScreen extends ModularScreen { + + public GregTechGuiScreen(ModularPanel mainPanel) { + this(mainPanel, GTGuiTheme.STANDARD); + } + + public GregTechGuiScreen(ModularPanel mainPanel, GTGuiTheme theme) { + this(GTValues.MODID, mainPanel, theme); + } + + public GregTechGuiScreen(String owner, ModularPanel mainPanel, GTGuiTheme theme) { + this(owner, mainPanel, theme.getId()); + } + + public GregTechGuiScreen(String owner, ModularPanel mainPanel, String themeId) { + super(owner, mainPanel); + useTheme(themeId); + } +} diff --git a/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java b/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java new file mode 100644 index 00000000000..918aad93833 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java @@ -0,0 +1,79 @@ +package gregtech.api.mui.factory; + +import gregtech.api.capability.GregtechTileCapabilities; +import gregtech.api.cover.Cover; +import gregtech.api.cover.CoverHolder; +import gregtech.api.cover.CoverWithUI; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class CoverGuiFactory extends AbstractUIFactory { + + public static final CoverGuiFactory INSTANCE = new CoverGuiFactory(); + + private CoverGuiFactory() { + super("gregtech:cover"); + } + + public static > void open(EntityPlayer player, T cover) { + Objects.requireNonNull(player); + Objects.requireNonNull(cover); + if (!cover.getCoverableView().isValid()) { + throw new IllegalArgumentException("Can't open Cover GUI on invalid cover holder!"); + } + if (player.world != cover.getWorld()) { + throw new IllegalArgumentException("Cover must be in same dimension as the player!"); + } + BlockPos pos = cover.getPos(); + SidedPosGuiData data = new SidedPosGuiData(player, pos.getX(), pos.getY(), pos.getZ(), cover.getAttachedSide()); + GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(SidedPosGuiData data) { + TileEntity te = data.getTileEntity(); + if (te == null) { + throw new IllegalStateException("Could not get gui for null TileEntity!"); + } + CoverHolder coverHolder = te.getCapability(GregtechTileCapabilities.CAPABILITY_COVER_HOLDER, data.getSide()); + if (coverHolder == null) { + throw new IllegalStateException("Could not get CoverHolder for found TileEntity!"); + } + Cover cover = coverHolder.getCoverAtSide(data.getSide()); + if (cover == null) { + throw new IllegalStateException("Could not find cover at side " + data.getSide() + + " for found CoverHolder!"); + } + if (!(cover instanceof CoverWithUI coverWithUI)) { + throw new IllegalStateException("Cover at side " + data.getSide() + " is not a gui holder!"); + } + return coverWithUI; + } + + @Override + public void writeGuiData(SidedPosGuiData guiData, PacketBuffer buffer) { + buffer.writeVarInt(guiData.getX()); + buffer.writeVarInt(guiData.getY()); + buffer.writeVarInt(guiData.getZ()); + buffer.writeByte(guiData.getSide().getIndex()); + } + + @Override + public @NotNull SidedPosGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new SidedPosGuiData(player, buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt(), + EnumFacing.VALUES[buffer.readByte()]); + } +} diff --git a/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java b/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java new file mode 100644 index 00000000000..7fea957a5a1 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java @@ -0,0 +1,56 @@ +package gregtech.api.mui.factory; + +import gregtech.api.items.metaitem.MetaItem; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.EnumHand; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.HandGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class MetaItemGuiFactory extends AbstractUIFactory { + + public static final MetaItemGuiFactory INSTANCE = new MetaItemGuiFactory(); + + private MetaItemGuiFactory() { + super("gregtech:meta_item"); + } + + public static void open(EntityPlayer player, EnumHand hand) { + Objects.requireNonNull(player); + Objects.requireNonNull(hand); + HandGuiData guiData = new HandGuiData(player, hand); + GuiManager.open(INSTANCE, guiData, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(HandGuiData data) { + ItemStack stack = data.getUsedItemStack(); + if (!(stack.getItem() instanceof MetaItemmetaItem)) { + throw new IllegalArgumentException("Found item is not a valid MetaItem!"); + } + MetaItem.MetaValueItem valueItem = metaItem.getItem(stack); + if (valueItem == null || valueItem.getUIManager() == null) { + throw new IllegalArgumentException("Found MetaItem is not a gui holder!"); + } + return valueItem.getUIManager(); + } + + @Override + public void writeGuiData(HandGuiData guiData, PacketBuffer buffer) { + buffer.writeByte(guiData.getHand().ordinal()); + } + + @Override + public @NotNull HandGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new HandGuiData(player, EnumHand.values()[buffer.readByte()]); + } +} diff --git a/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java b/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java new file mode 100644 index 00000000000..0363476a987 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java @@ -0,0 +1,63 @@ +package gregtech.api.mui.factory; + +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.PosGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class MetaTileEntityGuiFactory extends AbstractUIFactory { + + public static final MetaTileEntityGuiFactory INSTANCE = new MetaTileEntityGuiFactory(); + + private MetaTileEntityGuiFactory() { + super("gregtech:mte"); + } + + public static > void open(EntityPlayer player, T mte) { + Objects.requireNonNull(player); + Objects.requireNonNull(mte); + if (!mte.isValid()) { + throw new IllegalArgumentException("Can't open invalid MetaTileEntity GUI!"); + } + if (player.world != mte.getWorld()) { + throw new IllegalArgumentException("MetaTileEntity must be in same dimension as the player!"); + } + BlockPos pos = mte.getPos(); + PosGuiData data = new PosGuiData(player, pos.getX(), pos.getY(), pos.getZ()); + GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(PosGuiData data) { + TileEntity te = data.getTileEntity(); + if (te instanceof IGregTechTileEntity gtte) { + MetaTileEntity mte = gtte.getMetaTileEntity(); + return Objects.requireNonNull(castGuiHolder(mte), "Found MetaTileEntity is not a gui holder!"); + } + throw new IllegalStateException("Found TileEntity is not a MetaTileEntity!"); + } + + @Override + public void writeGuiData(PosGuiData guiData, PacketBuffer buffer) { + buffer.writeVarInt(guiData.getX()); + buffer.writeVarInt(guiData.getY()); + buffer.writeVarInt(guiData.getZ()); + } + + @Override + public @NotNull PosGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new PosGuiData(player, buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt()); + } +} diff --git a/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java b/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java new file mode 100644 index 00000000000..bd848099117 --- /dev/null +++ b/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java @@ -0,0 +1,213 @@ +package gregtech.api.mui.widget; + +import gregtech.api.capability.impl.GhostCircuitItemStackHandler; +import gregtech.api.mui.GTGuiTextures; +import gregtech.api.mui.GTGuis; +import gregtech.api.recipes.ingredients.IntCircuitIngredient; +import gregtech.client.utils.TooltipHelper; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraftforge.items.IItemHandler; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.utils.MouseData; +import com.cleanroommc.modularui.value.sync.ItemSlotSH; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.layout.Grid; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GhostCircuitSlotWidget extends ItemSlot { + + private static final int SYNC_CIRCUIT_INDEX = 10; + + public GhostCircuitSlotWidget() { + tooltipBuilder(this::getCircuitSlotTooltip); + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + if (!isSelectorPanelOpen()) { + if (mouseButton == 0 && TooltipHelper.isShiftDown()) { + createSelectorPanel(); + } else { + MouseData mouseData = MouseData.create(mouseButton); + getSyncHandler().syncToServer(2, mouseData::writeToPacket); + } + } + return Result.SUCCESS; + } + + @Override + public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { + if (isSelectorPanelOpen()) return true; + MouseData mouseData = MouseData.create(scrollDirection.modifier); + getSyncHandler().syncToServer(3, mouseData::writeToPacket); + return false; + } + + @Override + public ItemSlot slot(ModularSlot slot) { + ItemSlotSH sh = new GhostCircuitSyncHandler(slot); + isValidSyncHandler(sh); + setSyncHandler(sh); + return this; + } + + @Override + protected List getItemTooltip(ItemStack stack) { + // we don't want the item tooltip + return Collections.emptyList(); + } + + protected void getCircuitSlotTooltip(@NotNull Tooltip tooltip) { + String configString; + int value = getSyncHandler().getGhostCircuitHandler().getCircuitValue(); + if (value == GhostCircuitItemStackHandler.NO_CONFIG) { + configString = new TextComponentTranslation("gregtech.gui.configurator_slot.no_value").getFormattedText(); + } else { + configString = String.valueOf(value); + } + + tooltip.addLine(IKey.lang("gregtech.gui.configurator_slot.tooltip", configString)); + } + + @Override + public @NotNull GhostCircuitSyncHandler getSyncHandler() { + return (GhostCircuitSyncHandler) super.getSyncHandler(); + } + + @Override + public void onMouseDrag(int mouseButton, long timeSinceClick) {} + + @Override + public boolean onMouseRelease(int mouseButton) { + return true; + } + + private boolean isSelectorPanelOpen() { + return getPanel().getScreen().isPanelOpen("circuit_selector"); + } + + private void createSelectorPanel() { + ItemDrawable circuitPreview = new ItemDrawable(getSyncHandler().getSlot().getStack()); + + List> options = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + options.add(new ArrayList<>()); + for (int j = 0; j < 9; j++) { + int index = i * 9 + j; + if (index > 32) break; + options.get(i).add(new ButtonWidget<>() + .size(18) + .background(GTGuiTextures.SLOT, new ItemDrawable( + IntCircuitIngredient.getIntegratedCircuit(index)).asIcon()) + .disableHoverBackground() + .onMousePressed(mouseButton -> { + getSyncHandler().syncToServer(SYNC_CIRCUIT_INDEX, buf -> buf.writeShort(index)); + circuitPreview.setItem(IntCircuitIngredient.getIntegratedCircuit(index)); + return true; + })); + } + } + + getPanel().getScreen().openPanel(GTGuis.createPopupPanel("circuit_selector", 176, 120) + .child(IKey.lang("metaitem.circuit.integrated.gui").asWidget().pos(5, 5)) + .child(circuitPreview.asIcon().size(16).asWidget() + .size(18) + .top(19).alignX(0.5f) + .background(GTGuiTextures.SLOT, GTGuiTextures.INT_CIRCUIT_OVERLAY)) + .child(new Grid() + .left(7).right(7).top(41).height(4 * 18) + .matrix(options) + .minColWidth(18).minRowHeight(18) + .minElementMargin(0, 0))); + } + + private static class GhostCircuitSyncHandler extends ItemSlotSH { + + public GhostCircuitSyncHandler(ModularSlot slot) { + super(slot); + } + + @Override + protected void phantomClick(MouseData mouseData) { + if (mouseData.mouseButton == 0) { + // increment on left-click + setCircuitValue(getNextCircuitValue(1)); + } else if (mouseData.mouseButton == 1 && mouseData.shift) { + // clear on shift-right-click + setCircuitValue(GhostCircuitItemStackHandler.NO_CONFIG); + } else if (mouseData.mouseButton == 1) { + // decrement on right-click + setCircuitValue(getNextCircuitValue(-1)); + } + } + + @Override + protected void phantomScroll(MouseData mouseData) { + setCircuitValue(getNextCircuitValue(mouseData.mouseButton)); + } + + private void setCircuitValue(int value) { + GhostCircuitItemStackHandler handler = getGhostCircuitHandler(); + if (handler.getCircuitValue() != value) { + handler.setCircuitValue(value); + syncToClient(1, buf -> { + buf.writeBoolean(false); + buf.writeItemStack(handler.getStackInSlot(0)); + buf.writeBoolean(false); + }); + } + } + + @Override + public void readOnServer(int id, PacketBuffer buf) throws IOException { + if (id == SYNC_CIRCUIT_INDEX) { + setCircuitValue(buf.readShort()); + } else { + super.readOnServer(id, buf); + } + } + + private int getNextCircuitValue(int delta) { + GhostCircuitItemStackHandler handler = getGhostCircuitHandler(); + + // if no circuit, skip 0 and return 32 if decrementing, + // or, skip 0 and return 1 when incrementing + if (!handler.hasCircuitValue()) { + return delta == 1 ? 1 : IntCircuitIngredient.CIRCUIT_MAX; + // if at max, loop around to no circuit + } else if (handler.getCircuitValue() + delta > IntCircuitIngredient.CIRCUIT_MAX) { + return GhostCircuitItemStackHandler.NO_CONFIG; + // if at 1, skip 0 and return to no circuit + } else if (handler.getCircuitValue() + delta < 1) { + return GhostCircuitItemStackHandler.NO_CONFIG; + } + + // normal case: change by "delta" which is either 1 or -1 + return handler.getCircuitValue() + delta; + } + + public GhostCircuitItemStackHandler getGhostCircuitHandler() { + IItemHandler handler = getSlot().getItemHandler(); + if (!(handler instanceof GhostCircuitItemStackHandler ghostHandler)) { + throw new IllegalStateException( + "GhostCircuitSyncHandler has IItemHandler that is not GhostCircuitItemStackHandler"); + } + return ghostHandler; + } + } +} diff --git a/src/main/java/gregtech/api/pipenet/PipeNet.java b/src/main/java/gregtech/api/pipenet/PipeNet.java index 384d15134a1..536b2a23b81 100644 --- a/src/main/java/gregtech/api/pipenet/PipeNet.java +++ b/src/main/java/gregtech/api/pipenet/PipeNet.java @@ -15,8 +15,13 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.*; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; public abstract class PipeNet implements INBTSerializable { @@ -63,6 +68,11 @@ public void onPipeConnectionsUpdate() {} public void onNeighbourUpdate(BlockPos fromPos) {} + /** + * Is called when any Pipe TE in the PipeNet is unloaded + */ + public void onChunkUnload() {} + public Map> getAllNodes() { return unmodifiableNodeByBlockPos; } diff --git a/src/main/java/gregtech/api/pipenet/WorldPipeNet.java b/src/main/java/gregtech/api/pipenet/WorldPipeNet.java index 23860461888..03211cc377c 100644 --- a/src/main/java/gregtech/api/pipenet/WorldPipeNet.java +++ b/src/main/java/gregtech/api/pipenet/WorldPipeNet.java @@ -1,5 +1,7 @@ package gregtech.api.pipenet; +import gregtech.api.util.GTLog; + import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.EnumFacing; @@ -12,7 +14,11 @@ import org.jetbrains.annotations.NotNull; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class WorldPipeNet> extends WorldSavedData { @@ -35,9 +41,16 @@ protected void setWorldAndInit(World world) { } } - public static String getDataID(final String baseID, final World world) { - if (world == null || world.isRemote) - throw new RuntimeException("WorldPipeNet should only be created on the server!"); + public static @NotNull String getDataID(@NotNull final String baseID, @NotNull final World world) { + // noinspection ConstantValue + if (world == null || world.isRemote) { + GTLog.logger.error("WorldPipeNet should only be created on the server!", new Throwable()); + // noinspection ConstantValue + if (world == null) { + return baseID; + } + } + int dimension = world.provider.getDimension(); return dimension == 0 ? baseID : baseID + '.' + dimension; } diff --git a/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java b/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java index a2d593bb206..84897a022e8 100644 --- a/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java +++ b/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java @@ -26,6 +26,7 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.Constants.NBT; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -539,6 +540,19 @@ public boolean shouldRefresh(@NotNull World world, @NotNull BlockPos pos, IBlock return oldState.getBlock() != newSate.getBlock(); } + @MustBeInvokedByOverriders + @Override + public void onChunkUnload() { + super.onChunkUnload(); + if (!world.isRemote) { + WorldPipeNet worldPipeNet = getPipeBlock().getWorldPipeNet(getWorld()); + PipeNet net = worldPipeNet.getNetFromPos(pos); + if (net != null) { + net.onChunkUnload(); + } + } + } + public void doExplosion(float explosionPower) { getWorld().setBlockToAir(getPos()); if (!getWorld().isRemote) { diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index e09519f4dde..b291f994301 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -562,23 +562,24 @@ public Pair, List> getFluidAndChanceOutputs outputs.addAll(GTUtility.copyFluidList(getFluidOutputs())); } // If just the regular outputs would satisfy the outputLimit - else if (getOutputs().size() >= outputLimit) { + else if (getFluidOutputs().size() >= outputLimit) { outputs.addAll( - GTUtility.copyFluidList(getFluidOutputs()).subList(0, Math.min(outputLimit, getOutputs().size()))); + GTUtility.copyFluidList(getFluidOutputs()).subList(0, + Math.min(outputLimit, getFluidOutputs().size()))); // clear the chanced outputs, as we are only getting regular outputs chancedOutputs.clear(); } // If the regular outputs and chanced outputs are required to satisfy the outputLimit - else if (!getOutputs().isEmpty() && (getOutputs().size() + chancedOutputs.size()) >= outputLimit) { + else if (!getFluidOutputs().isEmpty() && (getFluidOutputs().size() + chancedOutputs.size()) >= outputLimit) { outputs.addAll(GTUtility.copyFluidList(getFluidOutputs())); // Calculate the number of chanced outputs after adding all the regular outputs - int numChanced = outputLimit - getOutputs().size(); + int numChanced = outputLimit - getFluidOutputs().size(); chancedOutputs = chancedOutputs.subList(0, Math.min(numChanced, chancedOutputs.size())); } // There are only chanced outputs to satisfy the outputLimit - else if (getOutputs().isEmpty()) { + else if (getFluidOutputs().isEmpty()) { chancedOutputs = chancedOutputs.subList(0, Math.min(outputLimit, chancedOutputs.size())); } // The number of outputs + chanced outputs is lower than the trim number, so just add everything diff --git a/src/main/java/gregtech/api/recipes/RecipeMap.java b/src/main/java/gregtech/api/recipes/RecipeMap.java index c73fc20bb74..2f9417afe27 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMap.java +++ b/src/main/java/gregtech/api/recipes/RecipeMap.java @@ -4,21 +4,30 @@ import gregtech.api.GregTechAPI; import gregtech.api.capability.IMultipleTankHandler; import gregtech.api.capability.impl.FluidTankList; -import gregtech.api.gui.GuiTextures; import gregtech.api.gui.ModularUI; import gregtech.api.gui.resources.TextureArea; import gregtech.api.gui.widgets.ProgressWidget.MoveType; -import gregtech.api.gui.widgets.RecipeProgressWidget; -import gregtech.api.gui.widgets.SlotWidget; -import gregtech.api.gui.widgets.TankWidget; import gregtech.api.recipes.category.GTRecipeCategory; import gregtech.api.recipes.chance.boost.ChanceBoostFunction; import gregtech.api.recipes.ingredients.GTRecipeInput; import gregtech.api.recipes.ingredients.IntCircuitIngredient; -import gregtech.api.recipes.map.*; +import gregtech.api.recipes.map.AbstractMapIngredient; +import gregtech.api.recipes.map.Branch; +import gregtech.api.recipes.map.Either; +import gregtech.api.recipes.map.MapFluidIngredient; +import gregtech.api.recipes.map.MapItemStackIngredient; +import gregtech.api.recipes.map.MapItemStackNBTIngredient; +import gregtech.api.recipes.map.MapOreDictIngredient; +import gregtech.api.recipes.map.MapOreDictNBTIngredient; +import gregtech.api.recipes.ui.RecipeMapUI; +import gregtech.api.recipes.ui.RecipeMapUIFunction; import gregtech.api.unification.material.Material; import gregtech.api.unification.ore.OrePrefix; -import gregtech.api.util.*; +import gregtech.api.util.EnumValidationResult; +import gregtech.api.util.GTLog; +import gregtech.api.util.GTUtility; +import gregtech.api.util.LocalizationUtils; +import gregtech.api.util.ValidationResult; import gregtech.common.ConfigHolder; import gregtech.integration.crafttweaker.CTRecipeHelper; import gregtech.integration.crafttweaker.recipe.CTRecipe; @@ -42,8 +51,6 @@ import crafttweaker.api.item.IItemStack; import crafttweaker.api.liquid.ILiquidStack; import crafttweaker.api.minecraft.CraftTweakerMC; -import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; -import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -51,13 +58,25 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import stanhebben.zenscript.annotations.*; import stanhebben.zenscript.annotations.Optional; +import stanhebben.zenscript.annotations.ZenClass; +import stanhebben.zenscript.annotations.ZenGetter; +import stanhebben.zenscript.annotations.ZenMethod; +import stanhebben.zenscript.annotations.ZenSetter; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; import java.util.function.Consumer; import java.util.function.DoubleSupplier; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -74,6 +93,7 @@ public class RecipeMap> { private static boolean foundInvalidRecipe = false; public static final ChanceBoostFunction DEFAULT_CHANCE_FUNCTION = ChanceBoostFunction.OVERCLOCK; + protected RecipeMapUI recipeMapUI; public ChanceBoostFunction chanceFunction = DEFAULT_CHANCE_FUNCTION; @@ -84,16 +104,13 @@ public class RecipeMap> { private int maxOutputs; private int maxFluidInputs; private int maxFluidOutputs; - private final boolean modifyItemInputs; - private final boolean modifyItemOutputs; - private final boolean modifyFluidInputs; - private final boolean modifyFluidOutputs; - protected final Byte2ObjectMap slotOverlays; - protected TextureArea specialTexture; - protected int[] specialTexturePosition; - protected TextureArea progressBarTexture; - protected MoveType moveType; - public final boolean isHidden; + + /** + * @deprecated {@link RecipeMapUI#isJEIVisible()} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated + public final boolean isHidden = false; private boolean allowEmptyOutput; @@ -121,7 +138,11 @@ public class RecipeMap> { * @param maxFluidOutputs the maximum fluid outputs * @param defaultRecipeBuilder the default RecipeBuilder for the RecipeMap * @param isHidden if the RecipeMap should have a category in JEI + * + * @deprecated {@link #RecipeMap(String, RecipeBuilder, Function, int, int, int, int)} */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public RecipeMap(@NotNull String unlocalizedName, int maxInputs, int maxOutputs, int maxFluidInputs, int maxFluidOutputs, @NotNull R defaultRecipeBuilder, @@ -146,7 +167,11 @@ public RecipeMap(@NotNull String unlocalizedName, * @param modifyFluidOutputs if modification of the maximum fluid output is permitted * @param defaultRecipeBuilder the default RecipeBuilder for the RecipeMap * @param isHidden if the RecipeMap should have a category in JEI + * + * @deprecated {@link #RecipeMap(String, RecipeBuilder, Function, int, int, int, int)} */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public RecipeMap(@NotNull String unlocalizedName, int maxInputs, boolean modifyItemInputs, int maxOutputs, boolean modifyItemOutputs, @@ -155,21 +180,49 @@ public RecipeMap(@NotNull String unlocalizedName, @NotNull R defaultRecipeBuilder, boolean isHidden) { this.unlocalizedName = unlocalizedName; - this.slotOverlays = new Byte2ObjectOpenHashMap<>(); - this.progressBarTexture = GuiTextures.PROGRESS_BAR_ARROW; - this.moveType = MoveType.HORIZONTAL; this.maxInputs = maxInputs; this.maxFluidInputs = maxFluidInputs; this.maxOutputs = maxOutputs; this.maxFluidOutputs = maxFluidOutputs; - this.modifyItemInputs = modifyItemInputs; - this.modifyItemOutputs = modifyItemOutputs; - this.modifyFluidInputs = modifyFluidInputs; - this.modifyFluidOutputs = modifyFluidOutputs; + defaultRecipeBuilder.setRecipeMap(this); + defaultRecipeBuilder + .category(GTRecipeCategory.create(GTValues.MODID, unlocalizedName, getTranslationKey(), this)); + this.recipeBuilderSample = defaultRecipeBuilder; + + this.recipeMapUI = new RecipeMapUI<>(this, modifyItemInputs, modifyItemOutputs, modifyFluidInputs, + modifyFluidOutputs); + this.recipeMapUI.setJEIVisible(!isHidden); + + RECIPE_MAP_REGISTRY.put(unlocalizedName, this); + + this.grsVirtualizedRecipeMap = GregTechAPI.moduleManager.isModuleEnabled(GregTechModules.MODULE_GRS) ? + new VirtualizedRecipeMap(this) : null; + } + + /** + * Create and register new instance of RecipeMap with specified properties. + * + * @param unlocalizedName the unlocalized name for the RecipeMap + * @param defaultRecipeBuilder the default RecipeBuilder for the RecipeMap + * @param recipeMapUI the ui to represent this recipemap + * @param maxInputs the maximum item inputs + * @param maxOutputs the maximum item outputs + * @param maxFluidInputs the maximum fluid inputs + * @param maxFluidOutputs the maximum fluid outputs + */ + public RecipeMap(@NotNull String unlocalizedName, @NotNull R defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI, int maxInputs, int maxOutputs, int maxFluidInputs, + int maxFluidOutputs) { + this.unlocalizedName = unlocalizedName; + this.recipeMapUI = recipeMapUI.apply(this); + + this.maxInputs = maxInputs; + this.maxFluidInputs = maxFluidInputs; + this.maxOutputs = maxOutputs; + this.maxFluidOutputs = maxFluidOutputs; - this.isHidden = isHidden; defaultRecipeBuilder.setRecipeMap(this); defaultRecipeBuilder .category(GTRecipeCategory.create(GTValues.MODID, unlocalizedName, getTranslationKey(), this)); @@ -181,12 +234,12 @@ public RecipeMap(@NotNull String unlocalizedName, } @ZenMethod - public static List> getRecipeMaps() { + public static List>> getRecipeMaps() { return ImmutableList.copyOf(RECIPE_MAP_REGISTRY.values()); } @ZenMethod - public static RecipeMap getByName(String unlocalizedName) { + public static RecipeMap> getByName(String unlocalizedName) { return RECIPE_MAP_REGISTRY.get(unlocalizedName); } @@ -200,7 +253,7 @@ public static boolean isFoundInvalidRecipe() { } public static void setFoundInvalidRecipe(boolean foundInvalidRecipe) { - RecipeMap.foundInvalidRecipe |= foundInvalidRecipe; + RecipeMap.foundInvalidRecipe = RecipeMap.foundInvalidRecipe || foundInvalidRecipe; OrePrefix currentOrePrefix = OrePrefix.getCurrentProcessingPrefix(); if (currentOrePrefix != null) { Material currentMaterial = OrePrefix.getCurrentMaterial(); @@ -211,19 +264,39 @@ public static void setFoundInvalidRecipe(boolean foundInvalidRecipe) { } } + /** + * @deprecated {@link RecipeMapUI#setProgressBar(TextureArea, MoveType)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public RecipeMap setProgressBar(TextureArea progressBar, MoveType moveType) { - this.progressBarTexture = progressBar; - this.moveType = moveType; + this.recipeMapUI.setProgressBar(progressBar, moveType); return this; } + /** + * @deprecated {@link RecipeMapUI#setItemSlotOverlay(TextureArea, boolean, boolean)} + * {@link RecipeMapUI#setFluidSlotOverlay(TextureArea, boolean, boolean)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public RecipeMap setSlotOverlay(boolean isOutput, boolean isFluid, TextureArea slotOverlay) { return this.setSlotOverlay(isOutput, isFluid, false, slotOverlay).setSlotOverlay(isOutput, isFluid, true, slotOverlay); } + /** + * @deprecated {@link RecipeMapUI#setItemSlotOverlay(TextureArea, boolean, boolean)} + * {@link RecipeMapUI#setFluidSlotOverlay(TextureArea, boolean, boolean)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public RecipeMap setSlotOverlay(boolean isOutput, boolean isFluid, boolean isLast, TextureArea slotOverlay) { - this.slotOverlays.put((byte) ((isOutput ? 2 : 0) + (isFluid ? 1 : 0) + (isLast ? 4 : 0)), slotOverlay); + if (isFluid) { + this.recipeMapUI.setFluidSlotOverlay(slotOverlay, isOutput, isLast); + } else { + this.recipeMapUI.setItemSlotOverlay(slotOverlay, isOutput, isLast); + } return this; } @@ -252,7 +325,7 @@ public RecipeMap setSmallRecipeMap(RecipeMap recipeMap) { return this; } - public RecipeMap getSmallRecipeMap() { + public RecipeMap> getSmallRecipeMap() { return smallRecipeMap; } @@ -791,153 +864,82 @@ private Recipe diveIngredientTreeFindRecipeCollisions(@NotNull List= 6 && fluidHandler.getTanks() >= 2 && !isOutputs) startInputsY -= 9; - for (int i = 0; i < itemSlotsToDown; i++) { - for (int j = 0; j < itemSlotsToLeft; j++) { - int slotIndex = i * itemSlotsToLeft + j; - if (slotIndex >= itemInputsCount) break; - int x = startInputsX + 18 * j; - int y = startInputsY + 18 * i; - addSlot(builder, x, y, slotIndex, itemHandler, fluidHandler, invertFluids, isOutputs); - } - } - if (wasGroup) startInputsY += 2; - if (fluidInputsCount > 0 || invertFluids) { - if (itemSlotsToDown >= fluidInputsCount && itemSlotsToLeft < 3) { - int startSpecX = isOutputs ? startInputsX + itemSlotsToLeft * 18 : startInputsX - 18; - for (int i = 0; i < fluidInputsCount; i++) { - int y = startInputsY + 18 * i; - addSlot(builder, startSpecX, y, i, itemHandler, fluidHandler, !invertFluids, isOutputs); - } - } else { - int startSpecY = startInputsY + itemSlotsToDown * 18; - for (int i = 0; i < fluidInputsCount; i++) { - int x = isOutputs ? startInputsX + 18 * (i % 3) : - startInputsX + itemSlotsToLeft * 18 - 18 - 18 * (i % 3); - int y = startSpecY + (i / 3) * 18; - addSlot(builder, x, y, i, itemHandler, fluidHandler, !invertFluids, isOutputs); - } - } - } - } + FluidTankList fluidHandler, boolean isOutputs, int yOffset) {} + /** + * @deprecated {@link RecipeMapUI#addSlot(ModularUI.Builder, int, int, int, IItemHandlerModifiable, FluidTankList, boolean, boolean)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated protected void addSlot(ModularUI.Builder builder, int x, int y, int slotIndex, IItemHandlerModifiable itemHandler, - FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) { - if (!isFluid) { - builder.widget(new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs).setBackgroundTexture( - getOverlaysForSlot(isOutputs, false, slotIndex == itemHandler.getSlots() - 1))); - } else { - builder.widget(new TankWidget(fluidHandler.getTankAt(slotIndex), x, y, 18, 18).setAlwaysShowFull(true) - .setBackgroundTexture(getOverlaysForSlot(isOutputs, true, slotIndex == fluidHandler.getTanks() - 1)) - .setContainerClicking(true, !isOutputs)); - } - } + FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) {} + /** + * @deprecated {@link RecipeMapUI#getOverlaysForSlot(boolean, boolean, boolean)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated protected TextureArea[] getOverlaysForSlot(boolean isOutput, boolean isFluid, boolean isLast) { - TextureArea base = isFluid ? GuiTextures.FLUID_SLOT : GuiTextures.SLOT; - byte overlayKey = (byte) ((isOutput ? 2 : 0) + (isFluid ? 1 : 0) + (isLast ? 4 : 0)); - if (slotOverlays.containsKey(overlayKey)) { - return new TextureArea[] { base, slotOverlays.get(overlayKey) }; - } - return new TextureArea[] { base }; - } - - protected static int[] determineSlotsGrid(int itemInputsCount) { - int itemSlotsToLeft; - int itemSlotsToDown; - double sqrt = Math.sqrt(itemInputsCount); - // if the number of input has an integer root - // return it. - if (sqrt % 1 == 0) { - itemSlotsToLeft = itemSlotsToDown = (int) sqrt; - } else if (itemInputsCount == 3) { - itemSlotsToLeft = 3; - itemSlotsToDown = 1; - } else { - // if we couldn't fit all into a perfect square, - // increase the amount of slots to the left - itemSlotsToLeft = (int) Math.ceil(sqrt); - itemSlotsToDown = itemSlotsToLeft - 1; - // if we still can't fit all the slots in a grid, - // increase the amount of slots on the bottom - if (itemInputsCount > itemSlotsToLeft * itemSlotsToDown) { - itemSlotsToDown = itemSlotsToLeft; - } - } - return new int[] { itemSlotsToLeft, itemSlotsToDown }; + return null; } /** - * This height is used to determine size of background texture on JEI. + * @deprecated {@link RecipeMapUI#getPropertyHeightShift()} */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated public int getPropertyHeightShift() { - int maxPropertyCount = 0; - if (shouldShiftWidgets()) { - for (Recipe recipe : getRecipeList()) { - if (recipe.getPropertyCount() > maxPropertyCount) - maxPropertyCount = recipe.getPropertyCount(); - } - } - return maxPropertyCount * 10; // GTRecipeWrapper#LINE_HEIGHT + return recipeMapUI.getPropertyHeightShift(); } + /** + * @deprecated {@link RecipeMapUI#shouldShiftWidgets()} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated private boolean shouldShiftWidgets() { - return getMaxInputs() + getMaxOutputs() >= 6 || - getMaxFluidInputs() + getMaxFluidOutputs() >= 6; + return false; } @Method(modid = GTValues.MODID_GROOVYSCRIPT) @@ -1259,16 +1261,23 @@ protected void buildFromItemStacks(@NotNull List> li } } + /** + * @deprecated {@link RecipeMapUI#setSpecialTexture(TextureArea, int, int, int, int)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated protected RecipeMap setSpecialTexture(int x, int y, int width, int height, TextureArea area) { - this.specialTexturePosition = new int[] { x, y, width, height }; - this.specialTexture = area; + recipeMapUI.setSpecialTexture(area, x, y, width, height); return this; } + /** + * @deprecated {@link RecipeMapUI#addSpecialTexture(ModularUI.Builder)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated protected ModularUI.Builder addSpecialTexture(ModularUI.Builder builder) { - builder.image(specialTexturePosition[0], specialTexturePosition[1], specialTexturePosition[2], - specialTexturePosition[3], specialTexture); - return builder; + return recipeMapUI.addSpecialTexture(builder); } public Collection getRecipeList() { @@ -1277,7 +1286,7 @@ public Collection getRecipeList() { .collect(Collectors.toList()); } - public SoundEvent getSound() { + public @Nullable SoundEvent getSound() { return sound; } @@ -1296,7 +1305,7 @@ public CTRecipe ctFindRecipe(long maxVoltage, IItemStack[] itemInputs, ILiquidSt @ZenGetter("recipes") @Method(modid = GTValues.MODID_CT) - public List ccGetRecipeList() { + public List ctGetRecipeList() { return getRecipeList().stream().map(recipe -> new CTRecipe(this, recipe)).collect(Collectors.toList()); } @@ -1326,10 +1335,9 @@ public R recipeBuilder() { * @param ingredients list of input ingredients. * @param branchMap the current branch in the recursion. */ - @Nullable - private Recipe recurseIngredientTreeRemove(@NotNull Recipe recipeToRemove, - @NotNull List> ingredients, - @NotNull Branch branchMap, int depth) { + private @Nullable Recipe recurseIngredientTreeRemove(@NotNull Recipe recipeToRemove, + @NotNull List> ingredients, + @NotNull Branch branchMap, int depth) { // for every ingredient for (List current : ingredients) { // for all possibilities as keys @@ -1397,10 +1405,11 @@ public int getMaxInputs() { @ZenSetter("maxInputs") public void setMaxInputs(int maxInputs) { - if (modifyItemInputs) { - this.maxInputs = Math.max(this.maxInputs, maxInputs); - } else { - throw new UnsupportedOperationException("Cannot change max item input amount for " + getUnlocalizedName()); + this.maxInputs = Math.max(this.maxInputs, maxInputs); + if (!recipeMapUI.canModifyItemInputs()) { + GTLog.logger.warn( + "RecipeMap {} ui does not support changing max item inputs. Replace with a supporting UI for proper behavior.", + getUnlocalizedName(), new Throwable()); } } @@ -1411,10 +1420,11 @@ public int getMaxOutputs() { @ZenSetter("maxOutputs") public void setMaxOutputs(int maxOutputs) { - if (modifyItemOutputs) { - this.maxOutputs = Math.max(this.maxOutputs, maxOutputs); - } else { - throw new UnsupportedOperationException("Cannot change max item output amount for " + getUnlocalizedName()); + this.maxOutputs = Math.max(this.maxOutputs, maxOutputs); + if (!recipeMapUI.canModifyItemOutputs()) { + GTLog.logger.warn( + "RecipeMap {} ui does not support changing max item outputs. Replace with a supporting UI for proper behavior.", + getUnlocalizedName(), new Throwable()); } } @@ -1425,10 +1435,11 @@ public int getMaxFluidInputs() { @ZenSetter("maxFluidInputs") public void setMaxFluidInputs(int maxFluidInputs) { - if (modifyFluidInputs) { - this.maxFluidInputs = Math.max(this.maxFluidInputs, maxFluidInputs); - } else { - throw new UnsupportedOperationException("Cannot change max fluid input amount for " + getUnlocalizedName()); + this.maxFluidInputs = Math.max(this.maxFluidInputs, maxFluidInputs); + if (!recipeMapUI.canModifyFluidInputs()) { + GTLog.logger.warn( + "RecipeMap {} ui does not support changing max fluid inputs. Replace with a supporting UI for proper behavior.", + getUnlocalizedName(), new Throwable()); } } @@ -1439,11 +1450,11 @@ public int getMaxFluidOutputs() { @ZenSetter("maxFluidOutputs") public void setMaxFluidOutputs(int maxFluidOutputs) { - if (modifyFluidOutputs) { - this.maxFluidOutputs = Math.max(this.maxFluidOutputs, maxFluidOutputs); - } else { - throw new UnsupportedOperationException( - "Cannot change max fluid output amount for " + getUnlocalizedName()); + this.maxFluidOutputs = Math.max(this.maxFluidOutputs, maxFluidOutputs); + if (!recipeMapUI.canModifyFluidOutputs()) { + GTLog.logger.warn( + "RecipeMap {} ui does not support changing max fluid outputs. Replace with a supporting UI for proper behavior.", + getUnlocalizedName(), new Throwable()); } } @@ -1458,10 +1469,28 @@ public Map> getRecipesByCategory() { return Collections.unmodifiableMap(recipeByCategory); } + /** + * @return the current ui for the recipemap + */ + public @NotNull RecipeMapUI getRecipeMapUI() { + return recipeMapUI; + } + + /** + * @param recipeMapUI the recipemap ui to set + */ + public void setRecipeMapUI(@NotNull RecipeMapUI recipeMapUI) { + if (this.recipeMapUI.recipeMap() != recipeMapUI.recipeMap()) { + throw new IllegalArgumentException("RecipeMap UI RecipeMap '" + recipeMapUI.recipeMap().unlocalizedName + + "' does not match this RecipeMap '" + this.unlocalizedName + "'"); + } + this.recipeMapUI = recipeMapUI; + } + @Override @ZenMethod public String toString() { - return "RecipeMap{" + "unlocalizedName='" + unlocalizedName + '\'' + '}'; + return "RecipeMap{" + unlocalizedName + '}'; } @Override diff --git a/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java new file mode 100644 index 00000000000..2d6e810cac8 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java @@ -0,0 +1,265 @@ +package gregtech.api.recipes; + +import gregtech.api.gui.resources.TextureArea; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.recipes.ui.RecipeMapUI; +import gregtech.api.recipes.ui.RecipeMapUIFunction; + +import net.minecraft.util.SoundEvent; + +import it.unimi.dsi.fastutil.bytes.Byte2ObjectArrayMap; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static gregtech.api.recipes.ui.RecipeMapUI.computeOverlayKey; + +public class RecipeMapBuilder> { + + private final String unlocalizedName; + private final B defaultRecipeBuilder; + + private final Byte2ObjectMap slotOverlays = new Byte2ObjectArrayMap<>(); + + private int itemInputs; + private boolean modifyItemInputs = true; + private int itemOutputs; + private boolean modifyItemOutputs = true; + private int fluidInputs; + private boolean modifyFluidInputs = true; + private int fluidOutputs; + private boolean modifyFluidOutputs = true; + + private @Nullable TextureArea progressBar; + private @Nullable ProgressWidget.MoveType moveType; + + private @Nullable TextureArea specialTexture; + private int @Nullable [] specialTextureLocation; + + private RecipeMapUIFunction recipeMapUIFunction = this::buildUI; + + private SoundEvent sound; + private boolean allowEmptyOutputs; + + /** + * @param unlocalizedName the name of the recipemap + * @param defaultRecipeBuilder the default recipe builder of the recipemap + */ + public RecipeMapBuilder(@NotNull String unlocalizedName, @NotNull B defaultRecipeBuilder) { + this.unlocalizedName = unlocalizedName; + this.defaultRecipeBuilder = defaultRecipeBuilder; + } + + /** + * @param itemInputs the amount of item inputs + * @return this + */ + public @NotNull RecipeMapBuilder itemInputs(int itemInputs) { + this.itemInputs = itemInputs; + return this; + } + + /** + * @param modifyItemInputs if item input limit modification should be allowed + * @return this + */ + public @NotNull RecipeMapBuilder modifyItemInputs(boolean modifyItemInputs) { + this.modifyItemInputs = modifyItemInputs; + return this; + } + + /** + * @param itemOutputs the amount of item outputs + * @return this + */ + public @NotNull RecipeMapBuilder itemOutputs(int itemOutputs) { + this.itemOutputs = itemOutputs; + return this; + } + + /** + * @param modifyItemOutputs if item output limit modification should be allowed + * @return this + */ + public @NotNull RecipeMapBuilder modifyItemOutputs(boolean modifyItemOutputs) { + this.modifyItemOutputs = modifyItemOutputs; + return this; + } + + /** + * @param fluidInputs the amount of fluid inputs + * @return this + */ + public @NotNull RecipeMapBuilder fluidInputs(int fluidInputs) { + this.fluidInputs = fluidInputs; + return this; + } + + /** + * @param modifyFluidInputs if fluid input limit modification should be allowed + * @return this + */ + public @NotNull RecipeMapBuilder modifyFluidInputs(boolean modifyFluidInputs) { + this.modifyFluidInputs = modifyFluidInputs; + return this; + } + + /** + * @param fluidOutputs the amount of fluid outputs + * @return this + */ + public @NotNull RecipeMapBuilder fluidOutputs(int fluidOutputs) { + this.fluidOutputs = fluidOutputs; + return this; + } + + /** + * @param modifyFluidOutputs if fluid output limit modification should be allowed + * @return this + */ + public @NotNull RecipeMapBuilder modifyFluidOutputs(boolean modifyFluidOutputs) { + this.modifyFluidOutputs = modifyFluidOutputs; + return this; + } + + /** + * @param progressBar the progress bar texture to use + * @return this + */ + public @NotNull RecipeMapBuilder progressBar(@Nullable TextureArea progressBar) { + this.progressBar = progressBar; + return this; + } + + /** + * @param progressBar the progress bar texture to use + * @param moveType the progress bar move type to use + * @return this + */ + public @NotNull RecipeMapBuilder progressBar(@Nullable TextureArea progressBar, + @Nullable ProgressWidget.MoveType moveType) { + this.progressBar = progressBar; + this.moveType = moveType; + return this; + } + + /** + * @param texture the texture to use + * @param isOutput if the slot is an output slot + * @return this + */ + public @NotNull RecipeMapBuilder itemSlotOverlay(@NotNull TextureArea texture, boolean isOutput) { + this.slotOverlays.put(computeOverlayKey(isOutput, false, false), texture); + this.slotOverlays.put(computeOverlayKey(isOutput, false, true), texture); + return this; + } + + /** + * @param texture the texture to use + * @param isOutput if the slot is an output slot + * @param isLastSlot if the slot is the last slot + * @return this + */ + public @NotNull RecipeMapBuilder itemSlotOverlay(@NotNull TextureArea texture, boolean isOutput, + boolean isLastSlot) { + this.slotOverlays.put(computeOverlayKey(isOutput, false, isLastSlot), texture); + return this; + } + + /** + * @param texture the texture to use + * @param isOutput if the slot is an output slot + * @return this + */ + public @NotNull RecipeMapBuilder fluidSlotOverlay(@NotNull TextureArea texture, boolean isOutput) { + this.slotOverlays.put(computeOverlayKey(isOutput, true, false), texture); + this.slotOverlays.put(computeOverlayKey(isOutput, true, true), texture); + return this; + } + + /** + * @param texture the texture to use + * @param isOutput if the slot is an output slot + * @param isLastSlot if the slot is the last slot + * @return this + */ + public @NotNull RecipeMapBuilder fluidSlotOverlay(@NotNull TextureArea texture, boolean isOutput, + boolean isLastSlot) { + this.slotOverlays.put(computeOverlayKey(isOutput, true, isLastSlot), texture); + return this; + } + + public @NotNull RecipeMapBuilder specialTexture(@NotNull TextureArea texture, int x, int y, int width, + int height) { + this.specialTexture = texture; + this.specialTextureLocation = new int[] { x, y, width, height }; + return this; + } + + /** + * @param recipeMapUIFunction the custom function for creating the RecipeMap's ui + * @return this + */ + public @NotNull RecipeMapBuilder ui(@NotNull RecipeMapUIFunction recipeMapUIFunction) { + this.recipeMapUIFunction = recipeMapUIFunction; + return this; + } + + /** + * @param recipeMap the recipemap associated with the ui + * @return the RecipeMap's ui + */ + private @NotNull RecipeMapUI buildUI(@NotNull RecipeMap recipeMap) { + RecipeMapUI ui = new RecipeMapUI<>(recipeMap, modifyItemInputs, modifyItemOutputs, modifyFluidInputs, + modifyFluidOutputs); + if (progressBar != null) { + ui.setProgressBarTexture(progressBar); + } + if (moveType != null) { + ui.setProgressBarMoveType(moveType); + } + if (specialTexture != null && specialTextureLocation != null) { + ui.setSpecialTexture(specialTexture, specialTextureLocation); + } + for (var entry : slotOverlays.byte2ObjectEntrySet()) { + ui.setSlotOverlay(entry.getByteKey(), entry.getValue()); + } + + return ui; + } + + /** + * @param sound the sound to use + * @return this + */ + public @NotNull RecipeMapBuilder sound(@NotNull SoundEvent sound) { + this.sound = sound; + return this; + } + + /** + * Make the recipemap accept recipes without any outputs + * + * @return this + */ + public @NotNull RecipeMapBuilder allowEmptyOutputs() { + this.allowEmptyOutputs = true; + return this; + } + + /** + * Do not call this twice. RecipeMapBuilders are not re-usable. + * + * @return a new RecipeMap + */ + public @NotNull RecipeMap build() { + RecipeMap recipeMap = new RecipeMap<>(unlocalizedName, defaultRecipeBuilder, this.recipeMapUIFunction, + itemInputs, + itemOutputs, fluidInputs, fluidOutputs); + recipeMap.setSound(sound); + if (allowEmptyOutputs) { + recipeMap.allowEmptyOutput(); + } + return recipeMap; + } +} diff --git a/src/main/java/gregtech/api/recipes/RecipeMaps.java b/src/main/java/gregtech/api/recipes/RecipeMaps.java index cd222d4105b..e61261ddd09 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMaps.java +++ b/src/main/java/gregtech/api/recipes/RecipeMaps.java @@ -4,9 +4,32 @@ import gregtech.api.gui.GuiTextures; import gregtech.api.gui.widgets.ProgressWidget; import gregtech.api.gui.widgets.ProgressWidget.MoveType; -import gregtech.api.recipes.builders.*; +import gregtech.api.recipes.builders.AssemblerRecipeBuilder; +import gregtech.api.recipes.builders.AssemblyLineRecipeBuilder; +import gregtech.api.recipes.builders.BlastRecipeBuilder; +import gregtech.api.recipes.builders.CircuitAssemblerRecipeBuilder; +import gregtech.api.recipes.builders.ComputationRecipeBuilder; +import gregtech.api.recipes.builders.FuelRecipeBuilder; +import gregtech.api.recipes.builders.FusionRecipeBuilder; +import gregtech.api.recipes.builders.GasCollectorRecipeBuilder; +import gregtech.api.recipes.builders.ImplosionRecipeBuilder; +import gregtech.api.recipes.builders.PrimitiveRecipeBuilder; +import gregtech.api.recipes.builders.SimpleRecipeBuilder; +import gregtech.api.recipes.builders.UniversalDistillationRecipeBuilder; import gregtech.api.recipes.ingredients.GTRecipeInput; -import gregtech.api.recipes.machines.*; +import gregtech.api.recipes.machines.RecipeMapAssemblyLine; +import gregtech.api.recipes.machines.RecipeMapFluidCanner; +import gregtech.api.recipes.machines.RecipeMapFormingPress; +import gregtech.api.recipes.machines.RecipeMapFurnace; +import gregtech.api.recipes.machines.RecipeMapResearchStation; +import gregtech.api.recipes.machines.RecipeMapScanner; +import gregtech.api.recipes.ui.RecipeMapUI; +import gregtech.api.recipes.ui.impl.AssemblyLineUI; +import gregtech.api.recipes.ui.impl.CokeOvenUI; +import gregtech.api.recipes.ui.impl.CrackerUnitUI; +import gregtech.api.recipes.ui.impl.DistillationTowerUI; +import gregtech.api.recipes.ui.impl.FormingPressUI; +import gregtech.api.recipes.ui.impl.ResearchStationUI; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.Materials; import gregtech.api.unification.stack.ItemMaterialInfo; @@ -24,21 +47,21 @@ /** * Notes: - * + *

* All Examples here are for creating recipes in a Java mod, either in GTCEu or an addon mod, for CraftTweaker examples, * see the wiki, where plenty of examples are listed. - * + *

* It is preferable to use GTValues.VA[VOLTAGE_TIER] instead of GTValues.V[VOLTAGE_TIER] for the Recipe's EUt, * as the former accounts for cable loss, preventing full Amp recipes which can be annoying to deal with. * */ @ZenClass("mods.gregtech.recipe.RecipeMaps") @ZenRegister -public class RecipeMaps { +public final class RecipeMaps { /** * Example: - * + * *

      * RecipeMap.ALLOY_SMELTER_RECIPES.recipeBuilder()
      *         .input(OrePrefix.ingot, Materials.Tin)
@@ -53,15 +76,18 @@ public class RecipeMaps {
      * Note that the use of OrePrefix ensures that OreDictionary Entries are used for the recipe.
      */
     @ZenProperty
-    public static final RecipeMap ALLOY_SMELTER_RECIPES = new RecipeMap<>("alloy_smelter", 2, 1, 0,
-            0, new SimpleRecipeBuilder(), false)
-                    .setSlotOverlay(false, false, GuiTextures.FURNACE_OVERLAY_1)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.FURNACE);
+    public static final RecipeMap ALLOY_SMELTER_RECIPES = new RecipeMapBuilder<>("alloy_smelter",
+            new SimpleRecipeBuilder())
+                    .itemInputs(2)
+                    .itemOutputs(1)
+                    .itemSlotOverlay(GuiTextures.FURNACE_OVERLAY_1, false)
+                    .progressBar(GuiTextures.PROGRESS_BAR_ARROW)
+                    .sound(GTSoundEvents.FURNACE)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.ARC_FURNACE_RECIPES.recipeBuilder()
      *         .input(OrePrefix.ingot, Materials.Iron)
@@ -89,20 +115,25 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap ARC_FURNACE_RECIPES = new RecipeMap<>("arc_furnace", 1, 9, 1, 1, - new SimpleRecipeBuilder(), false) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARC_FURNACE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ARC) + public static final RecipeMap ARC_FURNACE_RECIPES = new RecipeMapBuilder<>("arc_furnace", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(9) + .fluidInputs(1) + .fluidOutputs(1) + .progressBar(GuiTextures.PROGRESS_BAR_ARC_FURNACE) + .sound(GTSoundEvents.ARC) + .build() .onRecipeBuild(recipeBuilder -> { recipeBuilder.invalidateOnBuildAction(); if (recipeBuilder.getFluidInputs().isEmpty()) { - recipeBuilder.fluidInputs(Materials.Oxygen.getFluid(recipeBuilder.duration)); + recipeBuilder.fluidInputs(Materials.Oxygen.getFluid(recipeBuilder.getDuration())); } }); /** * Example: - * + * *
      * RecipeMaps.ASSEMBLER_RECIPES.recipeBuilder()
      *         .circuitMeta(2)
@@ -113,17 +144,21 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap ASSEMBLER_RECIPES = new RecipeMap<>("assembler", 9, 1, 1, 0, - new AssemblerRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_CIRCUIT, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ASSEMBLER) + public static final RecipeMap ASSEMBLER_RECIPES = new RecipeMapBuilder<>("assembler", + new AssemblerRecipeBuilder()) + .itemInputs(9) + .itemOutputs(1) + .fluidInputs(1) + .itemSlotOverlay(GuiTextures.CIRCUIT_OVERLAY, false) + .progressBar(GuiTextures.PROGRESS_BAR_CIRCUIT) + .sound(GTSoundEvents.ASSEMBLER) + .build() .onRecipeBuild(recipeBuilder -> { recipeBuilder.invalidateOnBuildAction(); - if (recipeBuilder.fluidInputs.size() == 1 && - recipeBuilder.fluidInputs.get(0).getInputFluidStack().getFluid() == - Materials.SolderingAlloy.getFluid()) { - int amount = recipeBuilder.fluidInputs.get(0).getInputFluidStack().amount; + var fluidInputs = recipeBuilder.getFluidInputs(); + if (fluidInputs.size() == 1 && fluidInputs.get(0).getInputFluidStack().getFluid() == + Materials.SolderingAlloy.getFluid()) { + int amount = fluidInputs.get(0).getInputFluidStack().amount; recipeBuilder.copy().clearFluidInputs().fluidInputs(Materials.Tin.getFluid(amount * 2)) .buildAndRegister(); @@ -142,7 +177,7 @@ public class RecipeMaps { /** * Example: - * + * *
      * RecipeMaps.ASSEMBLY_LINE_RECIPES.recipeBuilder()
      *         .input(OrePrefix.stickLong, Materials.SamariumMagnetic)
@@ -161,14 +196,12 @@ public class RecipeMaps {
      */
     @ZenProperty
     public static final RecipeMap ASSEMBLY_LINE_RECIPES = new RecipeMapAssemblyLine<>(
-            "assembly_line", 16, false, 1, false, 4, false, 1, false, new AssemblyLineRecipeBuilder(), false)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.ASSEMBLER)
+            "assembly_line", new AssemblyLineRecipeBuilder(), AssemblyLineUI::new)
                     .onRecipeBuild(AssemblyLineManager::createDefaultResearchRecipe);
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.AUTOCLAVE_RECIPES.recipeBuilder()
      *         .inputs(OreDictUnifier.get(OrePrefix.dust, Materials.Carbon, 16))
@@ -180,16 +213,21 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap AUTOCLAVE_RECIPES = new RecipeMap<>("autoclave", 2, 2, 1, 1, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.DUST_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.CRYSTAL_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_CRYSTALLIZATION, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.FURNACE); + public static final RecipeMap AUTOCLAVE_RECIPES = new RecipeMapBuilder<>("autoclave", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(2) + .fluidInputs(1) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, false) + .itemSlotOverlay(GuiTextures.CRYSTAL_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_CRYSTALLIZATION) + .sound(GTSoundEvents.FURNACE) + .build(); /** * Example: - * + * *
      * RecipeMap.BENDER_RECIPES.recipeBuilder()
      *         .input(OrePrefix.plate, Materials.Tin, 12)
@@ -203,16 +241,19 @@ public class RecipeMaps {
      * Just like other SimpleRecipeBuilder RecipeMaps, circuit can be used to easily set a circuit
      */
     @ZenProperty
-    public static final RecipeMap BENDER_RECIPES = new RecipeMap<>("bender", 2, 1, 0, 0,
-            new SimpleRecipeBuilder(), false)
-                    .setSlotOverlay(false, false, false, GuiTextures.BENDER_OVERLAY)
-                    .setSlotOverlay(false, false, true, GuiTextures.INT_CIRCUIT_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_BENDING, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.MOTOR);
+    public static final RecipeMap BENDER_RECIPES = new RecipeMapBuilder<>("bender",
+            new SimpleRecipeBuilder())
+                    .itemInputs(2)
+                    .itemOutputs(1)
+                    .itemSlotOverlay(GuiTextures.BENDER_OVERLAY, false, false)
+                    .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_BENDING)
+                    .sound(GTSoundEvents.MOTOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.BLAST_RECIPES.recipeBuilder()
      *         .inputs(OreDictUnifier.get(OrePrefix.dust, Materials.Glass),
@@ -228,19 +269,24 @@ public class RecipeMaps {
      * The Electric Blast Furnace requires specification of a blast furnace temperature through the builder call of
      * blastFurnaceTemp. This value will set the temperature required for the recipe to run, restricting recipes
      * to certain coils.
-     *
+     * 

* Anything with a Blast Furnace Temperature of greater than 1750K will also autogenerate a hot ingot and a hot * ingot * cooling recipe. */ @ZenProperty - public static final RecipeMap BLAST_RECIPES = new RecipeMap<>("electric_blast_furnace", 3, 3, 1, - 1, new BlastRecipeBuilder(), false) - .setSound(GTSoundEvents.FURNACE); + public static final RecipeMap BLAST_RECIPES = new RecipeMapBuilder<>("electric_blast_furnace", + new BlastRecipeBuilder()) + .itemInputs(3) + .itemOutputs(3) + .fluidInputs(1) + .fluidOutputs(1) + .sound(GTSoundEvents.FURNACE) + .build(); /** * Example: - * + * *

      * RecipeMap.BREWING_RECIPES.recipeBuilder()
      *         .input(MetaItems.BIO_CHAFF)
@@ -254,15 +300,19 @@ public class RecipeMaps {
      * Any Recipe added to the Brewery not specifying an EUt value will default 4.
      */
     @ZenProperty
-    public static final RecipeMap BREWING_RECIPES = new RecipeMap<>("brewery", 1, 0, 1, 1,
-            new SimpleRecipeBuilder().duration(128).EUt(4), false)
-                    .setSlotOverlay(false, false, GuiTextures.BREWER_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.CHEMICAL_REACTOR);
+    public static final RecipeMap BREWING_RECIPES = new RecipeMapBuilder<>("brewery",
+            new SimpleRecipeBuilder().duration(128).EUt(4))
+                    .itemInputs(1)
+                    .fluidInputs(1)
+                    .fluidOutputs(1)
+                    .itemSlotOverlay(GuiTextures.BREWER_OVERLAY, false)
+                    .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE)
+                    .sound(GTSoundEvents.CHEMICAL_REACTOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.CANNER_RECIPES.recipeBuilder()
      *         .input(MetaItems.BATTERY_HULL_LV)
@@ -278,19 +328,22 @@ public class RecipeMaps {
      * It will empty or fill any fluid handler, so there is no need to add explicit recipes for the fluid handlers.
      */
     @ZenProperty
-    public static final RecipeMap CANNER_RECIPES = new RecipeMapFluidCanner("canner", 2, 2, 1, 1,
-            new SimpleRecipeBuilder(), false)
-                    .setSlotOverlay(false, false, false, GuiTextures.CANNER_OVERLAY)
-                    .setSlotOverlay(false, false, true, GuiTextures.CANISTER_OVERLAY)
-                    .setSlotOverlay(true, false, GuiTextures.CANISTER_OVERLAY)
-                    .setSlotOverlay(false, true, GuiTextures.DARK_CANISTER_OVERLAY)
-                    .setSlotOverlay(true, true, GuiTextures.DARK_CANISTER_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_CANNER, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.BATH);
+    public static final RecipeMap CANNER_RECIPES = new RecipeMapFluidCanner("canner",
+            new SimpleRecipeBuilder(), recipeMap -> {
+
+                RecipeMapUI ui = new RecipeMapUI<>(recipeMap, true, true, true, true);
+                ui.setItemSlotOverlay(GuiTextures.CANNER_OVERLAY, false, false);
+                ui.setItemSlotOverlay(GuiTextures.CANISTER_OVERLAY, false, true);
+                ui.setItemSlotOverlay(GuiTextures.CANISTER_OVERLAY, true);
+                ui.setFluidSlotOverlay(GuiTextures.DARK_CANISTER_OVERLAY, false);
+                ui.setFluidSlotOverlay(GuiTextures.DARK_CANISTER_OVERLAY, true);
+                ui.setProgressBar(GuiTextures.PROGRESS_BAR_CANNER, ProgressWidget.MoveType.HORIZONTAL);
+                return ui;
+            });
 
     /**
      * Examples:
-     * 
+     *
      * 
      * RecipeMap.CENTRIFUGE_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.ImpureNaquadriaSolution.getFluid(2000))
@@ -304,21 +357,26 @@ public class RecipeMaps {
      *
      * Most Centrifuge recipes exist because of automatic material decomposition recipes, but non-decomposition recipes
      * can still be added to the centrifuge.
-     *
+     * 

* Any Centrifuge recipe not specifying an EUt value will have the value default to 5. */ @ZenProperty - public static final RecipeMap CENTRIFUGE_RECIPES = new RecipeMap<>("centrifuge", 2, 6, 1, 6, - new SimpleRecipeBuilder().EUt(5), false) - .setSlotOverlay(false, false, false, GuiTextures.EXTRACTOR_OVERLAY) - .setSlotOverlay(false, false, true, GuiTextures.CANISTER_OVERLAY) - .setSlotOverlay(false, true, true, GuiTextures.CENTRIFUGE_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_EXTRACT, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.CENTRIFUGE); + public static final RecipeMap CENTRIFUGE_RECIPES = new RecipeMapBuilder<>("centrifuge", + new SimpleRecipeBuilder().EUt(5)) + .itemInputs(2) + .itemOutputs(6) + .fluidInputs(1) + .fluidOutputs(6) + .itemSlotOverlay(GuiTextures.EXTRACTOR_OVERLAY, false, false) + .itemSlotOverlay(GuiTextures.CANISTER_OVERLAY, false, true) + .fluidSlotOverlay(GuiTextures.CENTRIFUGE_OVERLAY, false, true) + .progressBar(GuiTextures.PROGRESS_BAR_EXTRACT) + .sound(GTSoundEvents.CENTRIFUGE) + .build(); /** * Example: - * + * *

      * RecipeMap.CHEMICAL_BATH_RECIPES.recipeBuilder()
      *         .input(OrePrefix.gem, Materials.EnderEye)
@@ -329,17 +387,21 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap CHEMICAL_BATH_RECIPES = new RecipeMap<>("chemical_bath", 1, 6, 1, - 1, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, true, GuiTextures.BREWER_OVERLAY) - .setSlotOverlay(true, false, false, GuiTextures.DUST_OVERLAY) - .setSlotOverlay(true, false, true, GuiTextures.DUST_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_MIXER, MoveType.CIRCULAR) - .setSound(GTSoundEvents.BATH); + public static final RecipeMap CHEMICAL_BATH_RECIPES = new RecipeMapBuilder<>("chemical_bath", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(6) + .fluidInputs(1) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.BREWER_OVERLAY, false, true) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_MIXER, MoveType.CIRCULAR) + .sound(GTSoundEvents.BATH) + .build(); /** * Example: - * + * *
      *      RecipeMap.CHEMICAL_RECIPES.recipeBuilder()
      * 				.circuitMeta(1))
@@ -355,22 +417,27 @@ public class RecipeMaps {
      * The Chemical Reactor has a special action that is performed for any recipe added to its recipe map, seen in its
      * onRecipeBuild call. Any recipe that is added to the Chemical Reactor will also be added to the
      * Large Chemical Reactor recipe map, with matching inputs, outputs, EUt, and duration.
-     *
+     * 

* This action cannot be negated, unlike special build actions for other recipe maps. - * + *

* Any recipe added to the Chemical Reactor not specifying an EUt value will default to 30. */ @ZenProperty - public static final RecipeMap CHEMICAL_RECIPES = new RecipeMap<>("chemical_reactor", 2, 2, 3, - 2, new SimpleRecipeBuilder().EUt(VA[LV]), false) - .setSlotOverlay(false, false, false, GuiTextures.MOLECULAR_OVERLAY_1) - .setSlotOverlay(false, false, true, GuiTextures.MOLECULAR_OVERLAY_2) - .setSlotOverlay(false, true, false, GuiTextures.MOLECULAR_OVERLAY_3) - .setSlotOverlay(false, true, true, GuiTextures.MOLECULAR_OVERLAY_4) - .setSlotOverlay(true, false, GuiTextures.VIAL_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.VIAL_OVERLAY_2) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL) - .setSound(GTValues.FOOLS.get() ? GTSoundEvents.SCIENCE : GTSoundEvents.CHEMICAL_REACTOR) + public static final RecipeMap CHEMICAL_RECIPES = new RecipeMapBuilder<>("chemical_reactor", + new SimpleRecipeBuilder().EUt(VA[LV])) + .itemInputs(2) + .itemOutputs(2) + .fluidInputs(3) + .fluidOutputs(2) + .itemSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_1, false, false) + .itemSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_2, false, true) + .itemSlotOverlay(GuiTextures.VIAL_OVERLAY_1, true) + .fluidSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_3, false, false) + .fluidSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_4, false, true) + .fluidSlotOverlay(GuiTextures.VIAL_OVERLAY_2, true) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(GTValues.FOOLS.get() ? GTSoundEvents.SCIENCE : GTSoundEvents.CHEMICAL_REACTOR) + .build() .onRecipeBuild(recipeBuilder -> { recipeBuilder.invalidateOnBuildAction(); RecipeMaps.LARGE_CHEMICAL_RECIPES.recipeBuilder() @@ -381,14 +448,14 @@ public class RecipeMaps { .fluidOutputs(recipeBuilder.getFluidOutputs()) .chancedFluidOutputs(recipeBuilder.getChancedFluidOutputs()) .cleanroom(recipeBuilder.getCleanroom()) - .duration(recipeBuilder.duration) - .EUt(recipeBuilder.EUt) + .duration(recipeBuilder.getDuration()) + .EUt(recipeBuilder.getEUt()) .buildAndRegister(); }); /** * Example: - * + * *

      * RecipeMap.CIRCUIT_ASSEMBLER_RECIPES.recipeBuilder()
      *         .input(MetaItems.BASIC_CIRCUIT_BOARD)
@@ -405,40 +472,43 @@ public class RecipeMaps {
      * The Circuit Assembler has a special action that is performed for any recipe added to its recipe map, seen in its
      * onRecipeBuild call. Any recipe that is added to the Circuit Assembler that does not specify a fluid input
      * in the recipe will automatically have recipes generated using Soldering Alloy and Tin for the input fluids.
-     *
+     * 

* The amount of these fluids is based on the Soldering Multiplier, which is a special addition to the * Circuit Assembler Recipe Builder. It is called through .solderMultiplier(int multiplier) on the Recipe * Builder. * The Multiplier itself is limited to numbers between 1 and (64000 / 144) inclusive. - * + *

* This action can be negated by simply specifying a fluid input in the recipe. */ @ZenProperty - public static final RecipeMap CIRCUIT_ASSEMBLER_RECIPES = new RecipeMap<>( - "circuit_assembler", 6, 1, 1, 0, new CircuitAssemblerRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_CIRCUIT_ASSEMBLER, ProgressWidget.MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ASSEMBLER) + public static final RecipeMap CIRCUIT_ASSEMBLER_RECIPES = new RecipeMapBuilder<>( + "circuit_assembler", new CircuitAssemblerRecipeBuilder()) + .itemInputs(6) + .itemOutputs(1) + .fluidInputs(1) + .itemSlotOverlay(GuiTextures.CIRCUIT_OVERLAY, false) + .progressBar(GuiTextures.PROGRESS_BAR_CIRCUIT_ASSEMBLER) + .sound(GTSoundEvents.ASSEMBLER) + .build() .onRecipeBuild(recipeBuilder -> { recipeBuilder.invalidateOnBuildAction(); if (recipeBuilder.getFluidInputs().isEmpty()) { recipeBuilder.copy() .fluidInputs(Materials.SolderingAlloy.getFluid(Math.max(1, - (GTValues.L / 2) * ((CircuitAssemblerRecipeBuilder) recipeBuilder) - .getSolderMultiplier()))) + (GTValues.L / 2) * recipeBuilder.getSolderMultiplier()))) .buildAndRegister(); // Don't call buildAndRegister as we are mutating the original recipe and already in the // middle of a buildAndRegister call. // Adding a second call will result in duplicate recipe generation attempts recipeBuilder.fluidInputs(Materials.Tin.getFluid(Math.max(1, GTValues.L * - ((CircuitAssemblerRecipeBuilder) recipeBuilder).getSolderMultiplier()))); + recipeBuilder.getSolderMultiplier()))); } }); /** * Example: - * + * *

      * RecipeMap.COKE_OVEN_RECIPES.recipeBuilder()
      *         .input(OrePrefix.log, Materials.Wood)
@@ -451,13 +521,18 @@ public class RecipeMaps {
      * As a Primitive Machine, the Coke Oven does not need an EUt parameter specified for the Recipe Builder.
      */
     @ZenProperty
-    public static final RecipeMap COKE_OVEN_RECIPES = new RecipeMapCokeOven<>("coke_oven", 1,
-            false, 1, false, 0, false, 1, false, new PrimitiveRecipeBuilder(), false)
-                    .setSound(GTSoundEvents.FIRE);
+    public static final RecipeMap COKE_OVEN_RECIPES = new RecipeMapBuilder<>("coke_oven",
+            new PrimitiveRecipeBuilder())
+                    .itemInputs(1)
+                    .itemOutputs(1)
+                    .fluidOutputs(1)
+                    .ui(CokeOvenUI::new)
+                    .sound(GTSoundEvents.FIRE)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.COMPRESSOR_RECIPES.recipeBuilder()
      *         .input(OrePrefix.dust, Materials.Fireclay)
@@ -471,15 +546,18 @@ public class RecipeMaps {
      * Any Recipe added to the Compressor not specifying a duration value will default to 200.
      */
     @ZenProperty
-    public static final RecipeMap COMPRESSOR_RECIPES = new RecipeMap<>("compressor", 1, 1, 0, 0,
-            new SimpleRecipeBuilder().duration(200).EUt(2), false)
-                    .setSlotOverlay(false, false, GuiTextures.COMPRESSOR_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_COMPRESS, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.COMPRESSOR);
+    public static final RecipeMap COMPRESSOR_RECIPES = new RecipeMapBuilder<>("compressor",
+            new SimpleRecipeBuilder().duration(200).EUt(2))
+                    .itemInputs(1)
+                    .itemOutputs(1)
+                    .itemSlotOverlay(GuiTextures.COMPRESSOR_OVERLAY, false)
+                    .progressBar(GuiTextures.PROGRESS_BAR_COMPRESS)
+                    .sound(GTSoundEvents.COMPRESSOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      *      RecipeMap.CRACKING_RECIPES.recipeBuilder()
      *              .circuitMeta(1))
@@ -492,17 +570,18 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap CRACKING_RECIPES = new RecipeMapCrackerUnit<>("cracker", 1, true, - 0, true, 2, false, 2, true, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, true, GuiTextures.CRACKING_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.CRACKING_OVERLAY_2) - .setSlotOverlay(false, false, GuiTextures.CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_CRACKING, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.FIRE); + public static final RecipeMap CRACKING_RECIPES = new RecipeMapBuilder<>("cracker", + new SimpleRecipeBuilder()) + .itemInputs(1) + .fluidInputs(2) + .fluidOutputs(2) + .ui(CrackerUnitUI::new) + .sound(GTSoundEvents.FIRE) + .build(); /** * Example: - * + * *
      * RecipeMap.CUTTER_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.LOG, 1, GTValues.W))
@@ -515,37 +594,43 @@ public class RecipeMaps {
      * The Cutting Machine has a special action that will be performed when its recipe is built, signified by the
      * onRecipeBuild call. If there is no fluid input specified in the passed recipe for the Cutting Machine,
      * recipes will automatically be generated using Water, Distilled Water, and Lubricant.
-     *
+     * 

* The amount of these fluids used is some arcane formula, probably copied from GT5 - * + *

* To negate this onRecipeBuild action, simply add a fluid input to the recipe passed to the Cutter Recipe * Map. */ @ZenProperty - public static final RecipeMap CUTTER_RECIPES = new RecipeMap<>("cutter", 1, 2, 1, 0, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.SAWBLADE_OVERLAY) - .setSlotOverlay(true, false, false, GuiTextures.CUTTER_OVERLAY) - .setSlotOverlay(true, false, true, GuiTextures.DUST_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_SLICE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.CUT) + public static final RecipeMap CUTTER_RECIPES = new RecipeMapBuilder<>("cutter", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(2) + .fluidInputs(1) + .itemSlotOverlay(GuiTextures.SAWBLADE_OVERLAY, false) + .itemSlotOverlay(GuiTextures.CUTTER_OVERLAY, true, false) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true, true) + .progressBar(GuiTextures.PROGRESS_BAR_SLICE) + .sound(GTSoundEvents.CUT) + .build() .onRecipeBuild(recipeBuilder -> { recipeBuilder.invalidateOnBuildAction(); if (recipeBuilder.getFluidInputs().isEmpty()) { + int duration = recipeBuilder.getDuration(); + int eut = recipeBuilder.getEUt(); recipeBuilder .copy() .fluidInputs(Materials.Water.getFluid(Math.max(4, - Math.min(1000, recipeBuilder.duration * recipeBuilder.EUt / 320)))) - .duration(recipeBuilder.duration * 2) + Math.min(1000, duration * eut / 320)))) + .duration(duration * 2) .buildAndRegister(); recipeBuilder .copy() .fluidInputs(Materials.DistilledWater.getFluid(Math.max(3, - Math.min(750, recipeBuilder.duration * recipeBuilder.EUt / 426)))) - .duration((int) (recipeBuilder.duration * 1.5)) + Math.min(750, duration * eut / 426)))) + .duration((int) (duration * 1.5)) .buildAndRegister(); // Don't call buildAndRegister as we are mutating the original recipe and already in the @@ -553,15 +638,15 @@ public class RecipeMaps { // Adding a second call will result in duplicate recipe generation attempts recipeBuilder .fluidInputs(Materials.Lubricant.getFluid(Math.max(1, - Math.min(250, recipeBuilder.duration * recipeBuilder.EUt / 1280)))) - .duration(Math.max(1, recipeBuilder.duration)); + Math.min(250, duration * eut / 1280)))) + .duration(Math.max(1, duration)); } }); /** * Examples: - * + * *

      * RecipeMap.DISTILLATION_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.CoalTar.getFluid(1000))
@@ -582,17 +667,22 @@ public class RecipeMaps {
      * this recipe is built, 2 separate recipes will be created in the Distillery. One for fluid A into the first
      * output,
      * and the second for fluid A into the second output.
-     *
+     * 

* This behavior can be disabled by adding a .disableDistilleryRecipes() onto the recipe builder. */ @ZenProperty - public static final RecipeMap DISTILLATION_RECIPES = new RecipeMapDistillationTower( - "distillation_tower", 0, true, 1, true, 1, true, 12, false, new UniversalDistillationRecipeBuilder(), false) - .setSound(GTSoundEvents.CHEMICAL_REACTOR); + public static final RecipeMap DISTILLATION_RECIPES = new RecipeMapBuilder<>( + "distillation_tower", new UniversalDistillationRecipeBuilder()) + .itemOutputs(1) + .fluidInputs(1) + .fluidOutputs(12) + .ui(DistillationTowerUI::new) + .sound(GTSoundEvents.CHEMICAL_REACTOR) + .build(); /** * Example: - * + * *

      * RecipeMap.DISTILLERY_RECIPES.recipeBuilder()
      *         .circuitMeta(1)
@@ -604,18 +694,23 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap DISTILLERY_RECIPES = new RecipeMap<>("distillery", 1, 1, 1, 1, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, true, GuiTextures.BEAKER_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.BEAKER_OVERLAY_4) - .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY) - .setSlotOverlay(false, false, GuiTextures.INT_CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.BOILER); + public static final RecipeMap DISTILLERY_RECIPES = new RecipeMapBuilder<>("distillery", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(1) + .fluidInputs(1) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true) + .fluidSlotOverlay(GuiTextures.BEAKER_OVERLAY_1, false) + .fluidSlotOverlay(GuiTextures.BEAKER_OVERLAY_4, true) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(GTSoundEvents.BOILER) + .build(); /** * Examples: - * + * *
      * RecipeMap.ELECTROLYZER_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.SaltWater.getFluid(1000))
@@ -628,17 +723,22 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap ELECTROLYZER_RECIPES = new RecipeMap<>("electrolyzer", 2, 6, 1, - 6, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, false, GuiTextures.LIGHTNING_OVERLAY_1) - .setSlotOverlay(false, false, true, GuiTextures.CANISTER_OVERLAY) - .setSlotOverlay(false, true, true, GuiTextures.LIGHTNING_OVERLAY_2) - .setProgressBar(GuiTextures.PROGRESS_BAR_EXTRACT, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ELECTROLYZER); + public static final RecipeMap ELECTROLYZER_RECIPES = new RecipeMapBuilder<>("electrolyzer", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(6) + .fluidInputs(1) + .fluidOutputs(6) + .itemSlotOverlay(GuiTextures.LIGHTNING_OVERLAY_1, false, false) + .itemSlotOverlay(GuiTextures.CANISTER_OVERLAY, false, true) + .fluidSlotOverlay(GuiTextures.LIGHTNING_OVERLAY_2, false) + .progressBar(GuiTextures.PROGRESS_BAR_EXTRACT) + .sound(GTSoundEvents.ELECTROLYZER) + .build(); /** * Example: - * + * *
      * RecipeMap.ELECTROMAGNETIC_SEPARATOR_RECIPES.recipeBuilder()
      *         .input(OrePrefix.dustPure, Materials.Aluminium)
@@ -651,16 +751,19 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap ELECTROMAGNETIC_SEPARATOR_RECIPES = new RecipeMap<>( - "electromagnetic_separator", 1, 3, 0, 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.CRUSHED_ORE_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_MAGNET, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ARC); + public static final RecipeMap ELECTROMAGNETIC_SEPARATOR_RECIPES = new RecipeMapBuilder<>( + "electromagnetic_separator", new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(3) + .itemSlotOverlay(GuiTextures.CRUSHED_ORE_OVERLAY, false) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_MAGNET) + .sound(GTSoundEvents.ARC) + .build(); /** * Example: - * + * *
      * RecipeMap.EXTRACTOR_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(MetaBlocks.RUBBER_LEAVES, 16))
@@ -673,15 +776,19 @@ public class RecipeMaps {
      * Any Recipe added to the Extractor not specifying an duration value will default to 400.
      */
     @ZenProperty
-    public static final RecipeMap EXTRACTOR_RECIPES = new RecipeMap<>("extractor", 1, 1, 0, 1,
-            new SimpleRecipeBuilder().duration(400).EUt(2), false)
-                    .setSlotOverlay(false, false, GuiTextures.EXTRACTOR_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_EXTRACT, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.COMPRESSOR);
+    public static final RecipeMap EXTRACTOR_RECIPES = new RecipeMapBuilder<>("extractor",
+            new SimpleRecipeBuilder().duration(400).EUt(2))
+                    .itemInputs(1)
+                    .itemOutputs(1)
+                    .fluidOutputs(1)
+                    .itemSlotOverlay(GuiTextures.EXTRACTOR_OVERLAY, false)
+                    .progressBar(GuiTextures.PROGRESS_BAR_EXTRACT)
+                    .sound(GTSoundEvents.COMPRESSOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.EXTRUDER_RECIPES.recipeBuilder()
      *         .input(OrePrefix.ingot, Materials.BorosilicateGlass)
@@ -693,15 +800,18 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap EXTRUDER_RECIPES = new RecipeMap<>("extruder", 2, 1, 0, 0, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, true, GuiTextures.MOLD_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_EXTRUDER, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ARC); + public static final RecipeMap EXTRUDER_RECIPES = new RecipeMapBuilder<>("extruder", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(1) + .itemSlotOverlay(GuiTextures.MOLD_OVERLAY, false, true) + .progressBar(GuiTextures.PROGRESS_BAR_EXTRUDER) + .sound(GTSoundEvents.ARC) + .build(); /** * Example: - * + * *
      * RecipeMap.FERMENTING_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.Biomass.getFluid(100))
@@ -713,16 +823,20 @@ public class RecipeMaps {
      * Any Recipe added to the Fermenter not specifying an EUt value will default to 2.
      */
     @ZenProperty
-    public static final RecipeMap FERMENTING_RECIPES = new RecipeMap<>("fermenter", 1, 1, 1, 1,
-            new SimpleRecipeBuilder().EUt(2), false)
-                    .setSlotOverlay(false, false, true, GuiTextures.DUST_OVERLAY)
-                    .setSlotOverlay(true, false, true, GuiTextures.DUST_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.CHEMICAL_REACTOR);
+    public static final RecipeMap FERMENTING_RECIPES = new RecipeMapBuilder<>("fermenter",
+            new SimpleRecipeBuilder().EUt(2))
+                    .itemInputs(1)
+                    .itemOutputs(1)
+                    .fluidInputs(1)
+                    .fluidOutputs(1)
+                    .itemSlotOverlay(GuiTextures.DUST_OVERLAY, false, true)
+                    .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true, true)
+                    .sound(GTSoundEvents.CHEMICAL_REACTOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.FLUID_HEATER_RECIPES.recipeBuilder()
      *         .circuitMeta(1)
@@ -734,17 +848,20 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap FLUID_HEATER_RECIPES = new RecipeMap<>("fluid_heater", 1, 0, 1, - 1, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, true, GuiTextures.HEATING_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.HEATING_OVERLAY_2) - .setSlotOverlay(false, false, GuiTextures.INT_CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.BOILER); + public static final RecipeMap FLUID_HEATER_RECIPES = new RecipeMapBuilder<>("fluid_heater", + new SimpleRecipeBuilder()) + .itemInputs(1) + .fluidInputs(1) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true) + .fluidSlotOverlay(GuiTextures.HEATING_OVERLAY_1, false) + .fluidSlotOverlay(GuiTextures.HEATING_OVERLAY_2, true) + .sound(GTSoundEvents.BOILER) + .build(); /** * Example: - * + * *
      * RecipeMap.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
      *         .notConsumable(MetaItems.SHAPE_MOLD_CYLINDER)
@@ -756,15 +873,18 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap FLUID_SOLIDFICATION_RECIPES = new RecipeMap<>("fluid_solidifier", - 1, 1, 1, 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.SOLIDIFIER_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.COOLING); + public static final RecipeMap FLUID_SOLIDFICATION_RECIPES = new RecipeMapBuilder<>( + "fluid_solidifier", new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(1) + .fluidInputs(1) + .itemSlotOverlay(GuiTextures.SOLIDIFIER_OVERLAY, false) + .sound(GTSoundEvents.COOLING) + .build(); /** * Example: - * + * *
      * RecipeMap.FORGE_HAMMER_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.STONE))
@@ -775,16 +895,19 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap FORGE_HAMMER_RECIPES = new RecipeMap<>("forge_hammer", 1, 1, 0, - 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.HAMMER_OVERLAY) - .setSpecialTexture(78, 42, 20, 6, GuiTextures.PROGRESS_BAR_HAMMER_BASE) - .setProgressBar(GuiTextures.PROGRESS_BAR_HAMMER, MoveType.VERTICAL_DOWNWARDS) - .setSound(GTSoundEvents.FORGE_HAMMER); + public static final RecipeMap FORGE_HAMMER_RECIPES = new RecipeMapBuilder<>("forge_hammer", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(1) + .itemSlotOverlay(GuiTextures.HAMMER_OVERLAY, false) + .specialTexture(GuiTextures.PROGRESS_BAR_HAMMER_BASE, 78, 42, 20, 6) + .progressBar(GuiTextures.PROGRESS_BAR_HAMMER, MoveType.VERTICAL_DOWNWARDS) + .sound(GTSoundEvents.FORGE_HAMMER) + .build(); /** * Example: - * + * *
      * RecipeMap.FORMING_PRESS_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.STONE))
@@ -796,14 +919,12 @@ public class RecipeMaps {
      */
     @ZenProperty
     public static final RecipeMap FORMING_PRESS_RECIPES = new RecipeMapFormingPress(
-            "forming_press", 6, 1, 0, 0, new SimpleRecipeBuilder(), false)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_COMPRESS, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.COMPRESSOR);
+            "forming_press", new SimpleRecipeBuilder(), FormingPressUI::new);
 
     /**
      *
      * Example:
-     * 
+     *
      * 
      * RecipeMap.FURNACE_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.SAND))
@@ -816,20 +937,22 @@ public class RecipeMaps {
      * When looking up recipes from the GTCEu Furnaces, they will first check the Vanilla Furnace Recipe list, therefore
      * our Furnaces can perform any recipe that is in the Vanilla Furnace Recipe List. This also means there is no need
      * to add Furnace Recipes that duplicate Vanilla recipes.
-     *
+     * 

* However, when adding a recipe to our Furnace Recipe Map, these new recipes are not added to the Vanilla Furnace * Recipe List, so any recipes added will be exclusive to the GTCEu Furnaces. */ @ZenProperty - public static final RecipeMap FURNACE_RECIPES = new RecipeMapFurnace("electric_furnace", 1, 1, - 0, 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.FURNACE_OVERLAY_1) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.FURNACE); + public static final RecipeMap FURNACE_RECIPES = new RecipeMapFurnace("electric_furnace", + new SimpleRecipeBuilder(), recipeMap -> { + RecipeMapUI ui = new RecipeMapUI<>(recipeMap, true, true, true, true); + ui.setItemSlotOverlay(GuiTextures.FURNACE_OVERLAY_1, false); + ui.setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, ProgressWidget.MoveType.HORIZONTAL); + return ui; + }); /** * Example: - * + * *

      * RecipeMap.FUSION_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.Lithium.getFluid(16), Materials.Tungsten.getFluid(16))
@@ -843,29 +966,35 @@ public class RecipeMaps {
      * The Fusion Reactor requires an EUToStart call, which is used to gate recipes behind requiring different
      * tier
      * Fusion Reactors. This value must be greater than 0.
-     *
+     * 

* The Breakpoints for this value currently are: * MK1: 160MEU and lower * MK2: 160MEU - 320MEU * MK3: 320MEU - 640MEU */ @ZenProperty - public static final RecipeMap FUSION_RECIPES = new RecipeMap<>("fusion_reactor", 0, 0, 2, 1, - new FusionRecipeBuilder(), false) - .setProgressBar(GuiTextures.PROGRESS_BAR_FUSION, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ARC); + public static final RecipeMap FUSION_RECIPES = new RecipeMapBuilder<>("fusion_reactor", + new FusionRecipeBuilder()) + .fluidInputs(2) + .fluidOutputs(1) + .progressBar(GuiTextures.PROGRESS_BAR_FUSION) + .sound(GTSoundEvents.ARC) + .build(); @ZenProperty - public static final RecipeMap GAS_COLLECTOR_RECIPES = new RecipeMap<>("gas_collector", 1, - 0, 0, 1, new GasCollectorRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.INT_CIRCUIT_OVERLAY) - .setSlotOverlay(true, true, GuiTextures.CENTRIFUGE_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.COOLING); + public static final RecipeMap GAS_COLLECTOR_RECIPES = new RecipeMapBuilder<>( + "gas_collector", new GasCollectorRecipeBuilder()) + .itemInputs(1) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true) + .fluidSlotOverlay(GuiTextures.CENTRIFUGE_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR) + .sound(GTSoundEvents.COOLING) + .build(); /** * Example: - * + * *

      * RecipeMap.IMPLOSION_RECIPES.recipeBuilder()
      *         .input(OreDictUnifier.get(OrePrefix.gem, Materials.Coal, 64))
@@ -892,23 +1021,27 @@ public class RecipeMaps {
      * explosivesAmount(int amount), which will generate a recipe using TNT as the explosive, with the count of
      * TNT
      * being the passed amount. Note that this must be between 1 and 64 inclusive.
-     *
+     * 

* The second method is to use explosivesType(ItemStack item). In this case, the passed ItemStack will be * used * as the explosive, with the number of explosives being the count of the passed ItemStack. * Note that the count must be between 1 and 64 inclusive */ @ZenProperty - public static final RecipeMap IMPLOSION_RECIPES = new RecipeMap<>("implosion_compressor", 3, - 2, 0, 0, new ImplosionRecipeBuilder().duration(20).EUt(VA[LV]), false) - .setSlotOverlay(false, false, true, GuiTextures.IMPLOSION_OVERLAY_1) - .setSlotOverlay(false, false, false, GuiTextures.IMPLOSION_OVERLAY_2) - .setSlotOverlay(true, false, true, GuiTextures.DUST_OVERLAY) - .setSound(SoundEvents.ENTITY_GENERIC_EXPLODE); + public static final RecipeMap IMPLOSION_RECIPES = new RecipeMapBuilder<>( + "implosion_compressor", new ImplosionRecipeBuilder().duration(20).EUt(VA[LV])) + .itemInputs(3) + .itemOutputs(2) + .itemSlotOverlay(GuiTextures.IMPLOSION_OVERLAY_1, false, true) + .itemSlotOverlay(GuiTextures.IMPLOSION_OVERLAY_2, false, false) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true, true) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(SoundEvents.ENTITY_GENERIC_EXPLODE) + .build(); /** * Example: - * + * *

      * RecipeMap.LARGE_CHEMICAL_RECIPES.recipeBuilder()
      *         .fluidInputs(Materials.NitrogenDioxide.getFluid(4000))
@@ -922,25 +1055,30 @@ public class RecipeMaps {
      *
      * Note that any recipes added to the Large Chemical Reactor recipe map will be exclusive to the LCR, unlike
      * recipes added to the Chemical Reactor, which will be mirrored to the LCR.
-     *
+     * 

* Any Recipe added to the Large Chemical Reactor not specifying an EUt value will default to 30. */ @ZenProperty - public static final RecipeMap LARGE_CHEMICAL_RECIPES = new RecipeMap<>( - "large_chemical_reactor", 3, 3, 5, 4, new SimpleRecipeBuilder().EUt(VA[LV]), false) - .setSlotOverlay(false, false, false, GuiTextures.MOLECULAR_OVERLAY_1) - .setSlotOverlay(false, false, true, GuiTextures.MOLECULAR_OVERLAY_2) - .setSlotOverlay(false, true, false, GuiTextures.MOLECULAR_OVERLAY_3) - .setSlotOverlay(false, true, true, GuiTextures.MOLECULAR_OVERLAY_4) - .setSlotOverlay(true, false, GuiTextures.VIAL_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.VIAL_OVERLAY_2) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.CHEMICAL_REACTOR) + public static final RecipeMap LARGE_CHEMICAL_RECIPES = new RecipeMapBuilder<>( + "large_chemical_reactor", new SimpleRecipeBuilder().EUt(VA[LV])) + .itemInputs(3) + .itemOutputs(3) + .fluidInputs(5) + .fluidOutputs(4) + .itemSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_1, false, false) + .itemSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_2, false, true) + .itemSlotOverlay(GuiTextures.VIAL_OVERLAY_1, true) + .fluidSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_3, false, false) + .fluidSlotOverlay(GuiTextures.MOLECULAR_OVERLAY_4, false, true) + .fluidSlotOverlay(GuiTextures.VIAL_OVERLAY_2, true) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(GTSoundEvents.CHEMICAL_REACTOR) + .build() .setSmallRecipeMap(CHEMICAL_RECIPES); /** * Example: - * + * *

      * RecipeMap.LASER_ENGRAVER_RECIPES.recipeBuilder()
      *         .input(MetaItems.SILICON_WAFER)
@@ -952,15 +1090,17 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap LASER_ENGRAVER_RECIPES = new RecipeMap<>("laser_engraver", 2, 1, - 0, 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, true, GuiTextures.LENS_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ELECTROLYZER); + public static final RecipeMap LASER_ENGRAVER_RECIPES = new RecipeMapBuilder<>("laser_engraver", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(1) + .itemSlotOverlay(GuiTextures.LENS_OVERLAY, false, true) + .sound(GTSoundEvents.ELECTROLYZER) + .build(); /** * Example: - * + * *
      * RecipeMap.LATHE_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.WOODEN_SLAB, 1, GTValues.W))
@@ -971,18 +1111,21 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap LATHE_RECIPES = new RecipeMap<>("lathe", 1, 2, 0, 0, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.PIPE_OVERLAY_1) - .setSlotOverlay(true, false, false, GuiTextures.PIPE_OVERLAY_2) - .setSlotOverlay(true, false, true, GuiTextures.DUST_OVERLAY) - .setSpecialTexture(98, 24, 5, 18, GuiTextures.PROGRESS_BAR_LATHE_BASE) - .setProgressBar(GuiTextures.PROGRESS_BAR_LATHE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.CUT); + public static final RecipeMap LATHE_RECIPES = new RecipeMapBuilder<>("lathe", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(2) + .itemSlotOverlay(GuiTextures.PIPE_OVERLAY_1, false) + .itemSlotOverlay(GuiTextures.PIPE_OVERLAY_2, true, false) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true, true) + .specialTexture(GuiTextures.PROGRESS_BAR_LATHE_BASE, 98, 24, 5, 18) + .progressBar(GuiTextures.PROGRESS_BAR_LATHE) + .sound(GTSoundEvents.CUT) + .build(); /** * Example: - * + * *
      * RecipeMap.MACERATOR_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Items.CHICKEN))
@@ -995,30 +1138,37 @@ public class RecipeMaps {
      * Any Recipe added to the Macerator not specifying a duration value will default to 150.
      */
     @ZenProperty
-    public static final RecipeMap MACERATOR_RECIPES = new RecipeMap<>("macerator", 1, 4, 0, 0,
-            new SimpleRecipeBuilder().duration(150).EUt(2), false)
-                    .setSlotOverlay(false, false, GuiTextures.CRUSHED_ORE_OVERLAY)
-                    .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_MACERATE, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.MACERATOR);
+    public static final RecipeMap MACERATOR_RECIPES = new RecipeMapBuilder<>("macerator",
+            new SimpleRecipeBuilder().duration(150).EUt(2))
+                    .itemInputs(1)
+                    .itemOutputs(4)
+                    .itemSlotOverlay(GuiTextures.CRUSHED_ORE_OVERLAY, false)
+                    .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_MACERATE)
+                    .sound(GTSoundEvents.MACERATOR)
+                    .build();
 
     /**
      * Currently unused
      */
     @ZenProperty
     @SuppressWarnings("unused")
-    public static final RecipeMap MASS_FABRICATOR_RECIPES = new RecipeMap<>("mass_fabricator", 1,
-            0, 1, 2, new SimpleRecipeBuilder(), false)
-                    .setSlotOverlay(false, false, GuiTextures.ATOMIC_OVERLAY_1)
-                    .setSlotOverlay(false, true, GuiTextures.ATOMIC_OVERLAY_2)
-                    .setSlotOverlay(true, true, GuiTextures.POSITIVE_MATTER_OVERLAY)
-                    .setSlotOverlay(true, true, true, GuiTextures.NEUTRAL_MATTER_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_MASS_FAB, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.REPLICATOR);
+    public static final RecipeMap MASS_FABRICATOR_RECIPES = new RecipeMapBuilder<>(
+            "mass_fabricator", new SimpleRecipeBuilder())
+                    .itemInputs(1)
+                    .fluidInputs(1)
+                    .fluidOutputs(2)
+                    .itemSlotOverlay(GuiTextures.ATOMIC_OVERLAY_1, false)
+                    .fluidSlotOverlay(GuiTextures.ATOMIC_OVERLAY_2, false)
+                    .fluidSlotOverlay(GuiTextures.POSITIVE_MATTER_OVERLAY, true)
+                    .fluidSlotOverlay(GuiTextures.NEUTRAL_MATTER_OVERLAY, true, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_MASS_FAB)
+                    .sound(GTSoundEvents.REPLICATOR)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * 		RecipeMap.MIXER_RECIPES.recipeBuilder()
      * 				.input(OrePrefix.dust, Materials.Redstone, 5)
@@ -1030,16 +1180,21 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap MIXER_RECIPES = new RecipeMap<>("mixer", 6, 1, 2, 1, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.DUST_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_MIXER, MoveType.CIRCULAR) - .setSound(GTSoundEvents.MIXER); + public static final RecipeMap MIXER_RECIPES = new RecipeMapBuilder<>("mixer", + new SimpleRecipeBuilder()) + .itemInputs(6) + .itemOutputs(1) + .fluidInputs(2) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, false) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_MIXER, MoveType.CIRCULAR) + .sound(GTSoundEvents.MIXER) + .build(); /** * Example: - * + * *
      * 		RecipeMap.ORE_WASHER_RECIPES.recipeBuilder()
      * 				.input(OrePrefix.crushed, Materials.Aluminum)
@@ -1053,16 +1208,20 @@ public class RecipeMaps {
      * Any Recipe added to the Ore Washer not specifying a duration value will default to 400.
      */
     @ZenProperty
-    public static final RecipeMap ORE_WASHER_RECIPES = new RecipeMap<>("ore_washer", 2, 3, 1, 0,
-            new SimpleRecipeBuilder().duration(400).EUt(16), false)
-                    .setSlotOverlay(false, false, GuiTextures.CRUSHED_ORE_OVERLAY)
-                    .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_BATH, MoveType.CIRCULAR)
-                    .setSound(GTSoundEvents.BATH);
+    public static final RecipeMap ORE_WASHER_RECIPES = new RecipeMapBuilder<>("ore_washer",
+            new SimpleRecipeBuilder().duration(400).EUt(16))
+                    .itemInputs(2)
+                    .itemOutputs(3)
+                    .fluidInputs(1)
+                    .itemSlotOverlay(GuiTextures.CRUSHED_ORE_OVERLAY, false)
+                    .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_BATH, MoveType.CIRCULAR)
+                    .sound(GTSoundEvents.BATH)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * 		RecipeMap.PACKER_RECIPES.recipeBuilder()
      * 				.inputs(new ItemStack(Items.WHEAT, 9))
@@ -1076,16 +1235,19 @@ public class RecipeMaps {
      * Any Recipe added to the Packer not specifying a duration value will default to 10.
      */
     @ZenProperty
-    public static final RecipeMap PACKER_RECIPES = new RecipeMap<>("packer", 2, 2, 0, 0,
-            new SimpleRecipeBuilder().EUt(12).duration(10), false)
-                    .setSlotOverlay(false, false, true, GuiTextures.BOX_OVERLAY)
-                    .setSlotOverlay(true, false, GuiTextures.BOXED_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_UNPACKER, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.ASSEMBLER);
+    public static final RecipeMap PACKER_RECIPES = new RecipeMapBuilder<>("packer",
+            new SimpleRecipeBuilder().EUt(12).duration(10))
+                    .itemInputs(2)
+                    .itemOutputs(2)
+                    .itemSlotOverlay(GuiTextures.BOX_OVERLAY, false, true)
+                    .itemSlotOverlay(GuiTextures.BOXED_OVERLAY, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_UNPACKER)
+                    .sound(GTSoundEvents.ASSEMBLER)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.POLARIZER_RECIPES.recipeBuilder()
      *         .inputs(OreDictUnifier.get(OrePrefix.plate, Materials.Iron))
@@ -1096,14 +1258,17 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap POLARIZER_RECIPES = new RecipeMap<>("polarizer", 1, 1, 0, 0, - new SimpleRecipeBuilder(), false) - .setProgressBar(GuiTextures.PROGRESS_BAR_MAGNET, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ARC); + public static final RecipeMap POLARIZER_RECIPES = new RecipeMapBuilder<>("polarizer", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(1) + .progressBar(GuiTextures.PROGRESS_BAR_MAGNET) + .sound(GTSoundEvents.ARC) + .build(); /** * Example: - * + * *
      *      RecipeMap.PRIMITIVE_BLAST_FURNACE_RECIPES.recipeBuilder()
      *     			.input(OrePrefix.ingot, Materials.Iron)
@@ -1118,13 +1283,20 @@ public class RecipeMaps {
      * Recipe Builder.
      */
     @ZenProperty
-    public static final RecipeMap PRIMITIVE_BLAST_FURNACE_RECIPES = new RecipeMap<>(
-            "primitive_blast_furnace", 3, false, 3, false, 0, false, 0, false, new PrimitiveRecipeBuilder(), false)
-                    .setSound(GTSoundEvents.FIRE);
+    public static final RecipeMap PRIMITIVE_BLAST_FURNACE_RECIPES = new RecipeMapBuilder<>(
+            "primitive_blast_furnace", new PrimitiveRecipeBuilder())
+                    .itemInputs(3)
+                    .modifyItemInputs(false)
+                    .itemOutputs(3)
+                    .modifyItemOutputs(false)
+                    .modifyFluidInputs(false)
+                    .modifyFluidOutputs(false)
+                    .sound(GTSoundEvents.FIRE)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.PYROLYSE_RECIPES.recipeBuilder()
      *         .input(OrePrefix.log, Materials.Wood, 16)
@@ -1138,43 +1310,53 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap PYROLYSE_RECIPES = new RecipeMap<>("pyrolyse_oven", 2, 1, 1, 1, - new SimpleRecipeBuilder(), false) - .setSound(GTSoundEvents.FIRE); + public static final RecipeMap PYROLYSE_RECIPES = new RecipeMapBuilder<>("pyrolyse_oven", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(1) + .fluidInputs(1) + .fluidOutputs(1) + .sound(GTSoundEvents.FIRE) + .build(); /** * Currently unused */ @ZenProperty - public static final RecipeMap REPLICATOR_RECIPES = new RecipeMap<>("replicator", 1, 1, 2, 1, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.DATA_ORB_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.ATOMIC_OVERLAY_1) - .setSlotOverlay(true, true, GuiTextures.ATOMIC_OVERLAY_2) - .setSlotOverlay(false, true, GuiTextures.NEUTRAL_MATTER_OVERLAY) - .setSlotOverlay(false, true, true, GuiTextures.POSITIVE_MATTER_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_REPLICATOR, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.REPLICATOR); + @SuppressWarnings("unused") + public static final RecipeMap REPLICATOR_RECIPES = new RecipeMapBuilder<>("replicator", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(1) + .fluidInputs(2) + .fluidOutputs(1) + .itemSlotOverlay(GuiTextures.DATA_ORB_OVERLAY, false) + .itemSlotOverlay(GuiTextures.ATOMIC_OVERLAY_1, true) + .fluidSlotOverlay(GuiTextures.NEUTRAL_MATTER_OVERLAY, false) + .fluidSlotOverlay(GuiTextures.POSITIVE_MATTER_OVERLAY, false, true) + .fluidSlotOverlay(GuiTextures.ATOMIC_OVERLAY_2, true) + .progressBar(GuiTextures.PROGRESS_BAR_REPLICATOR) + .sound(GTSoundEvents.REPLICATOR) + .build(); @ZenProperty public static final RecipeMap RESEARCH_STATION_RECIPES = new RecipeMapResearchStation<>( - "research_station", 2, 1, 0, 0, new ComputationRecipeBuilder(), false) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSlotOverlay(false, false, GuiTextures.SCANNER_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.RESEARCH_STATION_OVERLAY) - .setSound(GTValues.FOOLS.get() ? GTSoundEvents.SCIENCE : GTSoundEvents.COMPUTATION); + "research_station", new ComputationRecipeBuilder(), ResearchStationUI::new); @ZenProperty - public static final RecipeMap ROCK_BREAKER_RECIPES = new RecipeMap<>("rock_breaker", 1, 4, 0, - 0, new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.DUST_OVERLAY) - .setSlotOverlay(true, false, GuiTextures.CRUSHED_ORE_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_MACERATE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.FIRE); + public static final RecipeMap ROCK_BREAKER_RECIPES = new RecipeMapBuilder<>("rock_breaker", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(4) + .itemSlotOverlay(GuiTextures.DUST_OVERLAY, false) + .itemSlotOverlay(GuiTextures.CRUSHED_ORE_OVERLAY, true) + .progressBar(GuiTextures.PROGRESS_BAR_MACERATE) + .sound(GTSoundEvents.FIRE) + .build(); /** * Example: - * + * *
      * RecipeMaps.SCANNER_RECIPES.recipeBuilder()
      *         .inputNBT(MetaItems.TOOL_DATA_STICK, NBTMatcher.ANY, NBTCondition.ANY)
@@ -1186,16 +1368,18 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap SCANNER_RECIPES = new RecipeMapScanner("scanner", 2, 1, 1, 0, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.DATA_ORB_OVERLAY) - .setSlotOverlay(false, false, true, GuiTextures.SCANNER_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.ELECTROLYZER); + public static final RecipeMap SCANNER_RECIPES = new RecipeMapScanner("scanner", + new SimpleRecipeBuilder(), recipeMap -> { + RecipeMapUI ui = new RecipeMapUI<>(recipeMap, true, true, true, true); + ui.setItemSlotOverlay(GuiTextures.DATA_ORB_OVERLAY, false, false); + ui.setItemSlotOverlay(GuiTextures.SCANNER_OVERLAY, false, true); + ui.setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, ProgressWidget.MoveType.HORIZONTAL); + return ui; + }); /** * Example: - * + * *
      * RecipeMap.SIFTER_RECIPES.recipeBuilder()
      *         .inputs(new ItemStack(Blocks.SAND))
@@ -1210,14 +1394,17 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap SIFTER_RECIPES = new RecipeMap<>("sifter", 1, 6, 0, 0, - new SimpleRecipeBuilder(), false) - .setProgressBar(GuiTextures.PROGRESS_BAR_SIFT, MoveType.VERTICAL_DOWNWARDS) - .setSound(SoundEvents.BLOCK_SAND_PLACE); + public static final RecipeMap SIFTER_RECIPES = new RecipeMapBuilder<>("sifter", + new SimpleRecipeBuilder()) + .itemInputs(1) + .itemOutputs(6) + .progressBar(GuiTextures.PROGRESS_BAR_SIFT, MoveType.VERTICAL_DOWNWARDS) + .sound(SoundEvents.BLOCK_SAND_PLACE) + .build(); /** * Example: - * + * *
      * RecipeMap.THERMAL_CENTRIFUGE_RECIPES.recipeBuilder()
      *         .input(OrePrefix.crushed, Materials.Aluminum)
@@ -1233,16 +1420,19 @@ public class RecipeMaps {
      * Any Recipe added to the Thermal Centrifuge not specifying a duration value will default to 400.
      */
     @ZenProperty
-    public static final RecipeMap THERMAL_CENTRIFUGE_RECIPES = new RecipeMap<>(
-            "thermal_centrifuge", 1, 3, 0, 0, new SimpleRecipeBuilder().duration(400).EUt(30), false)
-                    .setSlotOverlay(false, false, GuiTextures.CRUSHED_ORE_OVERLAY)
-                    .setSlotOverlay(true, false, GuiTextures.DUST_OVERLAY)
-                    .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, MoveType.HORIZONTAL)
-                    .setSound(GTSoundEvents.CENTRIFUGE);
+    public static final RecipeMap THERMAL_CENTRIFUGE_RECIPES = new RecipeMapBuilder<>(
+            "thermal_centrifuge", new SimpleRecipeBuilder().duration(400).EUt(30))
+                    .itemInputs(1)
+                    .itemOutputs(3)
+                    .itemSlotOverlay(GuiTextures.CRUSHED_ORE_OVERLAY, false)
+                    .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true)
+                    .progressBar(GuiTextures.PROGRESS_BAR_MACERATE)
+                    .sound(GTSoundEvents.CENTRIFUGE)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.VACUUM_RECIPES.recipeBuilder()
      *         .fluidInputs(Air.getFluid(4000))
@@ -1254,13 +1444,18 @@ public class RecipeMaps {
      * Any Recipe added to the Thermal Centrifuge not specifying an EUt value will default to 120.
      */
     @ZenProperty
-    public static final RecipeMap VACUUM_RECIPES = new RecipeMap<>("vacuum_freezer", 1, 1, 2, 1,
-            new SimpleRecipeBuilder().EUt(VA[MV]), false)
-                    .setSound(GTSoundEvents.COOLING);
+    public static final RecipeMap VACUUM_RECIPES = new RecipeMapBuilder<>("vacuum_freezer",
+            new SimpleRecipeBuilder().EUt(VA[MV]))
+                    .itemInputs(1)
+                    .itemOutputs(1)
+                    .fluidInputs(2)
+                    .fluidOutputs(1)
+                    .sound(GTSoundEvents.COOLING)
+                    .build();
 
     /**
      * Example:
-     * 
+     *
      * 
      * RecipeMap.WIREMILL_RECIPES.recipeBuilder()
      *         .input(OrePrefix.ingot, Materials.Iron)
@@ -1271,54 +1466,71 @@ public class RecipeMaps {
      * 
*/ @ZenProperty - public static final RecipeMap WIREMILL_RECIPES = new RecipeMap<>("wiremill", 2, 1, 0, 0, - new SimpleRecipeBuilder(), false) - .setSlotOverlay(false, false, GuiTextures.WIREMILL_OVERLAY) - .setSlotOverlay(false, false, true, GuiTextures.INT_CIRCUIT_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_WIREMILL, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.MOTOR); + public static final RecipeMap WIREMILL_RECIPES = new RecipeMapBuilder<>("wiremill", + new SimpleRecipeBuilder()) + .itemInputs(2) + .itemOutputs(1) + .itemSlotOverlay(GuiTextures.WIREMILL_OVERLAY, false) + .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true) + .progressBar(GuiTextures.PROGRESS_BAR_WIREMILL) + .sound(GTSoundEvents.MOTOR) + .build(); ////////////////////////////////////// // Fuel Recipe Maps // ////////////////////////////////////// @ZenProperty - public static final RecipeMap COMBUSTION_GENERATOR_FUELS = new RecipeMap<>( - "combustion_generator", 0, 0, 1, 0, new FuelRecipeBuilder(), false) - .setSlotOverlay(false, true, true, GuiTextures.FURNACE_OVERLAY_2) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.COMBUSTION) - .allowEmptyOutput(); + public static final RecipeMap COMBUSTION_GENERATOR_FUELS = new RecipeMapBuilder<>( + "combustion_generator", new FuelRecipeBuilder()) + .fluidInputs(1) + .fluidSlotOverlay(GuiTextures.FURNACE_OVERLAY_2, false) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(GTSoundEvents.COMBUSTION) + .allowEmptyOutputs() + .build(); @ZenProperty - public static final RecipeMap GAS_TURBINE_FUELS = new RecipeMap<>("gas_turbine", 0, 0, 1, 0, - new FuelRecipeBuilder(), false) - .setSlotOverlay(false, true, true, GuiTextures.DARK_CANISTER_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.TURBINE) - .allowEmptyOutput(); + public static final RecipeMap GAS_TURBINE_FUELS = new RecipeMapBuilder<>("gas_turbine", + new FuelRecipeBuilder()) + .fluidInputs(1) + .fluidSlotOverlay(GuiTextures.DARK_CANISTER_OVERLAY, false) + .progressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR) + .sound(GTSoundEvents.TURBINE) + .allowEmptyOutputs() + .build(); @ZenProperty - public static final RecipeMap STEAM_TURBINE_FUELS = new RecipeMap<>("steam_turbine", 0, 0, 1, 1, - new FuelRecipeBuilder(), false) - .setSlotOverlay(false, true, true, GuiTextures.CENTRIFUGE_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.TURBINE) - .allowEmptyOutput(); + public static final RecipeMap STEAM_TURBINE_FUELS = new RecipeMapBuilder<>("steam_turbine", + new FuelRecipeBuilder()) + .fluidInputs(1) + .fluidOutputs(1) + .fluidSlotOverlay(GuiTextures.CENTRIFUGE_OVERLAY, false) + .progressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR) + .sound(GTSoundEvents.TURBINE) + .allowEmptyOutputs() + .build(); @ZenProperty - public static final RecipeMap SEMI_FLUID_GENERATOR_FUELS = new RecipeMap<>( - "semi_fluid_generator", 0, 0, 1, 0, new FuelRecipeBuilder(), false) - .setSlotOverlay(false, true, true, GuiTextures.FURNACE_OVERLAY_2) - .setProgressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.COMBUSTION) - .allowEmptyOutput(); + public static final RecipeMap SEMI_FLUID_GENERATOR_FUELS = new RecipeMapBuilder<>( + "semi_fluid_generator", new FuelRecipeBuilder()) + .fluidInputs(1) + .fluidSlotOverlay(GuiTextures.FURNACE_OVERLAY_2, false) + .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) + .sound(GTSoundEvents.COMBUSTION) + .allowEmptyOutputs() + .build(); @ZenProperty - public static final RecipeMap PLASMA_GENERATOR_FUELS = new RecipeMap<>("plasma_generator", 0, 0, - 1, 1, new FuelRecipeBuilder(), false) - .setSlotOverlay(false, true, true, GuiTextures.CENTRIFUGE_OVERLAY) - .setProgressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR, MoveType.HORIZONTAL) - .setSound(GTSoundEvents.TURBINE) - .allowEmptyOutput(); + public static final RecipeMap PLASMA_GENERATOR_FUELS = new RecipeMapBuilder<>("plasma_generator", + new FuelRecipeBuilder()) + .fluidInputs(1) + .fluidOutputs(1) + .fluidSlotOverlay(GuiTextures.CENTRIFUGE_OVERLAY, false) + .progressBar(GuiTextures.PROGRESS_BAR_GAS_COLLECTOR) + .sound(GTSoundEvents.TURBINE) + .allowEmptyOutputs() + .build(); + + private RecipeMaps() {} } diff --git a/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java b/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java index 2d9d5006c1b..9ddc9d6851b 100644 --- a/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java @@ -155,6 +155,7 @@ public static class ResearchRecipeEntry { private final String researchId; private final ItemStack researchStack; private final ItemStack dataStack; + private final boolean ignoreNBT; private final int duration; private final int EUt; private final int CWUt; @@ -166,6 +167,9 @@ public static class ResearchRecipeEntry { * @param duration the duration of the recipe * @param EUt the EUt of the recipe * @param CWUt how much computation per tick this recipe needs if in Research Station + *

+ * By default, will ignore NBT on researchStack input. If NBT matching is desired, see + * {@link #ResearchRecipeEntry(String, ItemStack, ItemStack, boolean, int, int, int)} */ public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack researchStack, @NotNull ItemStack dataStack, int duration, int EUt, int CWUt) { @@ -175,6 +179,26 @@ public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack resear this.duration = duration; this.EUt = EUt; this.CWUt = CWUt; + this.ignoreNBT = true; + } + + /** + * @param researchId the id of the research to store + * @param researchStack the stack to scan for research + * @param dataStack the stack to contain the data + * @param duration the duration of the recipe + * @param EUt the EUt of the recipe + * @param CWUt how much computation per tick this recipe needs if in Research Station + */ + public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack researchStack, + @NotNull ItemStack dataStack, boolean ignoreNBT, int duration, int EUt, int CWUt) { + this.researchId = researchId; + this.researchStack = researchStack; + this.dataStack = dataStack; + this.ignoreNBT = ignoreNBT; + this.duration = duration; + this.EUt = EUt; + this.CWUt = CWUt; } @NotNull @@ -192,6 +216,10 @@ public ItemStack getDataStack() { return dataStack; } + public boolean getIgnoreNBT() { + return ignoreNBT; + } + public int getDuration() { return duration; } diff --git a/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java b/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java index bbbda50d520..b20da993e70 100644 --- a/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java @@ -15,12 +15,22 @@ public abstract class ResearchRecipeBuilder> protected ItemStack researchStack; protected ItemStack dataStack; + protected boolean ignoreNBT; protected String researchId; protected int eut; public T researchStack(@NotNull ItemStack researchStack) { if (!researchStack.isEmpty()) { this.researchStack = researchStack; + this.ignoreNBT = true; + } + return (T) this; + } + + public T researchStack(@NotNull ItemStack researchStack, boolean ignoreNBT) { + if (!researchStack.isEmpty()) { + this.researchStack = researchStack; + this.ignoreNBT = ignoreNBT; } return (T) this; } @@ -99,7 +109,8 @@ protected AssemblyLineRecipeBuilder.ResearchRecipeEntry build() { validateResearchItem(); if (duration <= 0) duration = DEFAULT_SCANNER_DURATION; if (eut <= 0) eut = DEFAULT_SCANNER_EUT; - return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, duration, + return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, ignoreNBT, + duration, eut, 0); } } @@ -148,7 +159,8 @@ protected AssemblyLineRecipeBuilder.ResearchRecipeEntry build() { int duration = totalCWU; if (eut <= 0) eut = DEFAULT_STATION_EUT; - return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, duration, + return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, ignoreNBT, + duration, eut, cwut); } } diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java index efd953ea099..28cd542d11d 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java @@ -1,83 +1,32 @@ package gregtech.api.recipes.machines; -import gregtech.api.capability.impl.FluidTankList; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.widgets.ProgressWidget; -import gregtech.api.gui.widgets.SlotWidget; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeBuilder; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.recipeproperties.ResearchProperty; import gregtech.api.recipes.recipeproperties.ResearchPropertyData; - -import net.minecraftforge.items.IItemHandlerModifiable; +import gregtech.api.recipes.ui.RecipeMapUIFunction; +import gregtech.core.sound.GTSoundEvents; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Map; +@ApiStatus.Internal public class RecipeMapAssemblyLine> extends RecipeMap implements IResearchRecipeMap { /** Contains the recipes for each research key */ private final Map> researchEntries = new Object2ObjectOpenHashMap<>(); - public RecipeMapAssemblyLine(String unlocalizedName, int maxInputs, boolean modifyItemInputs, int maxOutputs, - boolean modifyItemOutputs, - int maxFluidInputs, boolean modifyFluidInputs, int maxFluidOutputs, - boolean modifyFluidOutputs, R defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, modifyItemInputs, maxOutputs, modifyItemOutputs, maxFluidInputs, - modifyFluidInputs, maxFluidOutputs, modifyFluidOutputs, defaultRecipe, isHidden); - } - - @Override - @NotNull - public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, - FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { - ModularUI.Builder builder = ModularUI.builder(GuiTextures.BACKGROUND, 176, 176) - .widget(new ProgressWidget(200, 80, 1, 54, 72, GuiTextures.PROGRESS_BAR_ASSEMBLY_LINE, - ProgressWidget.MoveType.HORIZONTAL)) - .widget(new ProgressWidget(200, 138, 19, 10, 18, GuiTextures.PROGRESS_BAR_ASSEMBLY_LINE_ARROW, - ProgressWidget.MoveType.VERTICAL)); - this.addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); - this.addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); - return builder; - } - - @Override - protected void addInventorySlotGroup(ModularUI.Builder builder, @NotNull IItemHandlerModifiable itemHandler, - @NotNull FluidTankList fluidHandler, boolean isOutputs, int yOffset) { - int startInputsX = 80 - 4 * 18; - int fluidInputsCount = fluidHandler.getTanks(); - int startInputsY = 37 - 2 * 18; - - if (!isOutputs) { - // Data Slot - builder.widget(new SlotWidget(itemHandler, 16, startInputsX + 18 * 7, 1 + 18 * 2, true, true) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.DATA_ORB_OVERLAY)); - - // item input slots - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - int slotIndex = i * 4 + j; - addSlot(builder, startInputsX + 18 * j, startInputsY + 18 * i, slotIndex, itemHandler, fluidHandler, - false, false); - } - } - - // fluid slots - int startFluidX = startInputsX + 18 * 5; - for (int i = 0; i < 4; i++) { - addSlot(builder, startFluidX, startInputsY + 18 * i, i, itemHandler, fluidHandler, true, false); - } - } else { - // output slot - addSlot(builder, startInputsX + 18 * 7, 1, 0, itemHandler, fluidHandler, false, true); - } + public RecipeMapAssemblyLine(@NotNull String unlocalizedName, @NotNull R defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 16, 1, 4, 0); + setSound(GTSoundEvents.ASSEMBLER); } @Override @@ -103,8 +52,9 @@ public boolean removeRecipe(@NotNull Recipe recipe) { ResearchPropertyData data = recipe.getProperty(ResearchProperty.getInstance(), null); if (data != null) { for (ResearchPropertyData.ResearchEntry entry : data) { - return removeDataStickEntry(entry.getResearchId(), recipe); + removeDataStickEntry(entry.getResearchId(), recipe); } + return true; } return false; } diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapCrackerUnit.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapCrackerUnit.java deleted file mode 100644 index a0af84c51dc..00000000000 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapCrackerUnit.java +++ /dev/null @@ -1,73 +0,0 @@ -package gregtech.api.recipes.machines; - -import gregtech.api.capability.impl.FluidTankList; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.widgets.ProgressWidget; -import gregtech.api.gui.widgets.RecipeProgressWidget; -import gregtech.api.recipes.RecipeBuilder; -import gregtech.api.recipes.RecipeMap; - -import net.minecraftforge.items.IItemHandlerModifiable; - -import com.google.common.util.concurrent.AtomicDouble; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.function.DoubleSupplier; - -public class RecipeMapCrackerUnit> extends RecipeMap { - - public RecipeMapCrackerUnit(String unlocalizedName, int maxInputs, boolean modifyItemInputs, int maxOutputs, - boolean modifyItemOutputs, - int maxFluidInputs, boolean modifyFluidInputs, int maxFluidOutputs, - boolean modifyFluidOutputs, R defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, modifyItemInputs, maxOutputs, modifyItemOutputs, maxFluidInputs, - modifyFluidInputs, maxFluidOutputs, modifyFluidOutputs, defaultRecipe, isHidden); - } - - @Override - public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, - FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { - ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); - if (getMaxInputs() == 1) { - addSlot(builder, 52, 24 + yOffset, 0, importItems, importFluids, false, false); - } else { - int[] grid = determineSlotsGrid(getMaxInputs()); - for (int y = 0; y < grid[1]; y++) { - for (int x = 0; x < grid[0]; x++) { - addSlot(builder, 34 + (x * 18) - (Math.max(0, grid[0] - 2) * 18), - 24 + (y * 18) - (Math.max(0, grid[1] - 1) * 18) + yOffset, - y * grid[0] + x, importItems, importFluids, false, false); - } - } - } - - addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); - addSlot(builder, 52, 24 + yOffset + 19 + 18, 0, importItems, importFluids, true, false); - addSlot(builder, 34, 24 + yOffset + 19 + 18, 1, importItems, importFluids, true, false); - - Pair suppliers = createPairedSupplier(200, 41, 0.5); - builder.widget(new RecipeProgressWidget(suppliers.getLeft(), 42, 24 + yOffset + 18, 21, 19, - GuiTextures.PROGRESS_BAR_CRACKING_INPUT, ProgressWidget.MoveType.VERTICAL, this)); - builder.widget(new RecipeProgressWidget(suppliers.getRight(), 78, 23 + yOffset, 20, 20, progressBarTexture, - moveType, this)); - return builder; - } - - public static Pair createPairedSupplier(int ticksPerCycle, int width, - double splitPoint) { - AtomicDouble tracker = new AtomicDouble(0.0); - DoubleSupplier supplier1 = new ProgressWidget.TimedProgressSupplier(ticksPerCycle, width, false) { - - @Override - public double getAsDouble() { - double val = super.getAsDouble(); - tracker.set(val); - return val >= splitPoint ? 1.0 : (1.0 / splitPoint) * val; - } - }; - DoubleSupplier supplier2 = () -> tracker.get() >= splitPoint ? - (1.0 / (1 - splitPoint)) * (tracker.get() - splitPoint) : 0; - return Pair.of(supplier1, supplier2); - } -} diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapFluidCanner.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapFluidCanner.java index 19a32b23d0a..719cde4c311 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapFluidCanner.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapFluidCanner.java @@ -4,21 +4,27 @@ import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.builders.SimpleRecipeBuilder; import gregtech.api.recipes.ingredients.GTRecipeItemInput; +import gregtech.api.recipes.ui.RecipeMapUIFunction; +import gregtech.core.sound.GTSoundEvents; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +@ApiStatus.Internal public class RecipeMapFluidCanner extends RecipeMap { - public RecipeMapFluidCanner(String unlocalizedName, int maxInputs, int maxOutputs, int maxFluidInputs, - int maxFluidOutputs, SimpleRecipeBuilder defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, maxOutputs, maxFluidInputs, maxFluidOutputs, defaultRecipe, isHidden); + public RecipeMapFluidCanner(@NotNull String unlocalizedName, @NotNull SimpleRecipeBuilder defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 2, 2, 1, 1); + setSound(GTSoundEvents.BATH); } @Override diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapFormingPress.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapFormingPress.java index bbd546f7437..492ae4080c1 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapFormingPress.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapFormingPress.java @@ -1,33 +1,33 @@ package gregtech.api.recipes.machines; -import gregtech.api.capability.impl.FluidTankList; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.resources.TextureArea; -import gregtech.api.gui.widgets.SlotWidget; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.builders.SimpleRecipeBuilder; import gregtech.api.recipes.ingredients.GTRecipeItemInput; +import gregtech.api.recipes.ui.RecipeMapUIFunction; import gregtech.api.util.GTUtility; import gregtech.common.items.MetaItems; +import gregtech.core.sound.GTSoundEvents; import net.minecraft.item.ItemStack; import net.minecraftforge.common.util.Constants; import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.items.IItemHandlerModifiable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +@ApiStatus.Internal public class RecipeMapFormingPress extends RecipeMap { private static ItemStack NAME_MOLD = ItemStack.EMPTY; - public RecipeMapFormingPress(String unlocalizedName, int maxInputs, int maxOutputs, int maxFluidInputs, - int maxFluidOutputs, SimpleRecipeBuilder defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, maxOutputs, maxFluidInputs, maxFluidOutputs, defaultRecipe, isHidden); + public RecipeMapFormingPress(@NotNull String unlocalizedName, @NotNull SimpleRecipeBuilder defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 6, 1, 0, 0); + setSound(GTSoundEvents.COMPRESSOR); } @Override @@ -77,21 +77,4 @@ public Recipe findRecipe(long voltage, List inputs, List } return recipe; } - - @Override - protected void addSlot(ModularUI.Builder builder, int x, int y, int slotIndex, IItemHandlerModifiable itemHandler, - FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) { - SlotWidget slotWidget = new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs); - TextureArea base = GuiTextures.SLOT; - if (isOutputs) - slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_3); - else if (slotIndex == 0 || slotIndex == 3) - slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_2); - else if (slotIndex == 1 || slotIndex == 4) - slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_4); - else if (slotIndex == 2 || slotIndex == 5) - slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_1); - - builder.widget(slotWidget); - } } diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapFurnace.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapFurnace.java index cdfb13448c1..2c1b3d442e2 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapFurnace.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapFurnace.java @@ -4,23 +4,29 @@ import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.builders.SimpleRecipeBuilder; +import gregtech.api.recipes.ui.RecipeMapUIFunction; import gregtech.api.util.GTUtility; +import gregtech.core.sound.GTSoundEvents; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +@ApiStatus.Internal public class RecipeMapFurnace extends RecipeMap { public static final int RECIPE_EUT = 4; public static final int RECIPE_DURATION = 128; - public RecipeMapFurnace(String unlocalizedName, int maxInputs, int maxOutputs, int maxFluidInputs, - int maxFluidOutputs, SimpleRecipeBuilder defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, maxOutputs, maxFluidInputs, maxFluidOutputs, defaultRecipe, isHidden); + public RecipeMapFurnace(@NotNull String unlocalizedName, @NotNull SimpleRecipeBuilder defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 1, 1, 0, 0); + setSound(GTSoundEvents.FURNACE); } @Override diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapResearchStation.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapResearchStation.java index f75138d6f27..de0295359cc 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapResearchStation.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapResearchStation.java @@ -1,45 +1,20 @@ package gregtech.api.recipes.machines; -import gregtech.api.capability.impl.FluidTankList; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.widgets.ImageWidget; -import gregtech.api.gui.widgets.ProgressWidget; -import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.GTValues; import gregtech.api.recipes.RecipeBuilder; import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUIFunction; +import gregtech.core.sound.GTSoundEvents; -import net.minecraftforge.items.IItemHandlerModifiable; - -import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import java.util.function.DoubleSupplier; - +@ApiStatus.Internal public class RecipeMapResearchStation> extends RecipeMap implements IScannerRecipeMap { - public RecipeMapResearchStation(@NotNull String unlocalizedName, int maxInputs, int maxOutputs, - int maxFluidInputs, int maxFluidOutputs, @NotNull R defaultRecipeBuilder, - boolean isHidden) { - super(unlocalizedName, maxInputs, maxOutputs, maxFluidInputs, maxFluidOutputs, defaultRecipeBuilder, isHidden); - } - - @Override - @NotNull - public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, - FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { - Pair pairedSuppliers = RecipeMapCrackerUnit.createPairedSupplier(200, 90, 0.75); - return ModularUI.builder(GuiTextures.BACKGROUND, 176, 166) - .widget(new ImageWidget(10, 0, 84, 60, GuiTextures.PROGRESS_BAR_RESEARCH_STATION_BASE)) - .widget(new ProgressWidget(pairedSuppliers.getLeft(), 72, 28, 54, 5, - GuiTextures.PROGRESS_BAR_RESEARCH_STATION_1, ProgressWidget.MoveType.HORIZONTAL)) - .widget(new ProgressWidget(pairedSuppliers.getRight(), 119, 32, 10, 18, - GuiTextures.PROGRESS_BAR_RESEARCH_STATION_2, ProgressWidget.MoveType.VERTICAL_DOWNWARDS)) - .widget(new SlotWidget(importItems, 0, 115, 50, true, true) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.DATA_ORB_OVERLAY)) - .widget(new SlotWidget(importItems, 1, 43, 21, true, true) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.SCANNER_OVERLAY)) - .widget(new SlotWidget(exportItems, 0, 97, 21, true, true) - .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.RESEARCH_STATION_OVERLAY)); + public RecipeMapResearchStation(@NotNull String unlocalizedName, @NotNull R defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 2, 1, 0, 0); + setSound(GTValues.FOOLS.get() ? GTSoundEvents.SCIENCE : GTSoundEvents.COMPUTATION); } } diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapScanner.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapScanner.java index 472c00469a2..f76c7b10814 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapScanner.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapScanner.java @@ -3,23 +3,28 @@ import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.builders.SimpleRecipeBuilder; +import gregtech.api.recipes.ui.RecipeMapUIFunction; +import gregtech.core.sound.GTSoundEvents; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +@ApiStatus.Internal public class RecipeMapScanner extends RecipeMap implements IScannerRecipeMap { private static final List CUSTOM_SCANNER_LOGICS = new ArrayList<>(); - public RecipeMapScanner(String unlocalizedName, int maxInputs, int maxOutputs, int maxFluidInputs, - int maxFluidOutputs, SimpleRecipeBuilder defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, maxOutputs, maxFluidInputs, maxFluidOutputs, defaultRecipe, isHidden); + public RecipeMapScanner(@NotNull String unlocalizedName, @NotNull SimpleRecipeBuilder defaultRecipeBuilder, + @NotNull RecipeMapUIFunction recipeMapUI) { + super(unlocalizedName, defaultRecipeBuilder, recipeMapUI, 2, 1, 1, 0); + setSound(GTSoundEvents.ELECTROLYZER); } @Override diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java index 12fd90e7748..60188d8cd5f 100644 --- a/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java +++ b/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java @@ -6,6 +6,7 @@ import net.minecraft.client.resources.I18n; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.tuple.Pair; import java.util.Map; import java.util.TreeMap; @@ -14,7 +15,7 @@ public class FusionEUToStartProperty extends RecipeProperty { public static final String KEY = "eu_to_start"; - private static final TreeMap registeredFusionTiers = new TreeMap<>(); + private static final TreeMap> registeredFusionTiers = new TreeMap<>(); private static FusionEUToStartProperty INSTANCE; @@ -33,23 +34,29 @@ public static FusionEUToStartProperty getInstance() { @Override public void drawInfo(Minecraft minecraft, int x, int y, int color, Object value) { minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.eu_to_start", - TextFormattingUtil.formatLongToCompactString(castValue(value))) + getFusionTier(castValue(value)), x, y, + TextFormattingUtil.formatLongToCompactString(castValue(value))) + getFusionTierName(castValue(value)), + x, y, color); } - private static String getFusionTier(Long eu) { - Map.Entry mapEntry = registeredFusionTiers.ceilingEntry(eu); + private static String getFusionTierName(Long eu) { + Map.Entry> mapEntry = registeredFusionTiers.ceilingEntry(eu); if (mapEntry == null) { throw new IllegalArgumentException("Value is above registered maximum EU values"); } - return String.format(" %s", mapEntry.getValue()); + return String.format(" %s", mapEntry.getValue().getRight()); + } + + public static int getFusionTier(Long eu) { + Map.Entry> mapEntry = registeredFusionTiers.ceilingEntry(eu); + return mapEntry == null ? 0 : mapEntry.getValue().getLeft(); } public static void registerFusionTier(int tier, String shortName) { Validate.notNull(shortName); long maxEU = 16 * 10000000L * (long) Math.pow(2, tier - 6); - registeredFusionTiers.put(maxEU, shortName); + registeredFusionTiers.put(maxEU, Pair.of(tier, shortName)); } } diff --git a/src/main/java/gregtech/api/recipes/ui/RecipeMapUI.java b/src/main/java/gregtech/api/recipes/ui/RecipeMapUI.java new file mode 100644 index 00000000000..569efa103b6 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/RecipeMapUI.java @@ -0,0 +1,477 @@ +package gregtech.api.recipes.ui; + +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.gui.GuiTextures; +import gregtech.api.gui.ModularUI; +import gregtech.api.gui.resources.TextureArea; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.gui.widgets.RecipeProgressWidget; +import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.gui.widgets.TankWidget; +import gregtech.api.recipes.Recipe; +import gregtech.api.recipes.RecipeMap; + +import net.minecraftforge.items.IItemHandlerModifiable; + +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.function.DoubleSupplier; + +@ApiStatus.Experimental +public class RecipeMapUI> { + + private final Byte2ObjectMap slotOverlays = new Byte2ObjectOpenHashMap<>(); + + private final R recipeMap; + private final boolean modifyItemInputs; + private final boolean modifyItemOutputs; + private final boolean modifyFluidInputs; + private final boolean modifyFluidOutputs; + + private TextureArea progressBarTexture = GuiTextures.PROGRESS_BAR_ARROW; + private ProgressWidget.MoveType moveType = ProgressWidget.MoveType.HORIZONTAL; + private @Nullable TextureArea specialTexture; + private int @Nullable [] specialTexturePosition; + + private boolean isJEIVisible = true; + + /** + * @param recipeMap the recipemap corresponding to this ui + * @param modifyItemInputs if item input amounts can be modified + * @param modifyItemOutputs if item output amounts can be modified + * @param modifyFluidInputs if fluid input amounts can be modified + * @param modifyFluidOutputs if fluid output amounts can be modified + */ + public RecipeMapUI(@NotNull R recipeMap, boolean modifyItemInputs, boolean modifyItemOutputs, + boolean modifyFluidInputs, boolean modifyFluidOutputs) { + this.recipeMap = recipeMap; + this.modifyItemInputs = modifyItemInputs; + this.modifyItemOutputs = modifyItemOutputs; + this.modifyFluidInputs = modifyFluidInputs; + this.modifyFluidOutputs = modifyFluidOutputs; + } + + /** + * Compute the storage key for slot overlays. + * + * @param isOutput if the slot is an output slot + * @param isFluid if the slot is a fluid slot + * @param isLast if the slot is the last slot of its type + * @return the key + */ + @ApiStatus.Internal + public static byte computeOverlayKey(boolean isOutput, boolean isFluid, boolean isLast) { + return (byte) ((isOutput ? 2 : 0) + (isFluid ? 1 : 0) + (isLast ? 4 : 0)); + } + + /** + * Determines the slot grid for an item input amount + * + * @param itemInputsCount the item input amount + * @return [slots to the left, slots downwards] + */ + @Contract("_ -> new") + public static int @NotNull [] determineSlotsGrid(int itemInputsCount) { + int itemSlotsToLeft; + int itemSlotsToDown; + double sqrt = Math.sqrt(itemInputsCount); + // if the number of input has an integer root + // return it. + if (sqrt % 1 == 0) { + itemSlotsToLeft = (int) sqrt; + itemSlotsToDown = itemSlotsToLeft; + } else if (itemInputsCount == 3) { + itemSlotsToLeft = 3; + itemSlotsToDown = 1; + } else { + // if we couldn't fit all into a perfect square, + // increase the amount of slots to the left + itemSlotsToLeft = (int) Math.ceil(sqrt); + itemSlotsToDown = itemSlotsToLeft - 1; + // if we still can't fit all the slots in a grid, + // increase the amount of slots on the bottom + if (itemInputsCount > itemSlotsToLeft * itemSlotsToDown) { + itemSlotsToDown = itemSlotsToLeft; + } + } + return new int[] { itemSlotsToLeft, itemSlotsToDown }; + } + + /** + * Create a JEI UI Template + * + * @param importItems the input item inventory + * @param exportItems the output item inventory + * @param importFluids the input fluid inventory + * @param exportFluids the output fluid inventory + * @param yOffset the y offset for the gui + * @return the populated builder + */ + public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, + FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { + ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); + builder.widget(new RecipeProgressWidget(200, 78, 23 + yOffset, 20, 20, progressBarTexture, + moveType, recipeMap)); + addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); + addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); + if (specialTexture != null && specialTexturePosition != null) { + addSpecialTexture(builder); + } + return builder; + } + + /** + * This DOES NOT include machine control widgets or binds player inventory + * + * @param progressSupplier a supplier for the progress bar + * @param importItems the input item inventory + * @param exportItems the output item inventory + * @param importFluids the input fluid inventory + * @param exportFluids the output fluid inventory + * @param yOffset the y offset for the gui + * @return the populated builder + */ + public ModularUI.Builder createUITemplate(DoubleSupplier progressSupplier, IItemHandlerModifiable importItems, + IItemHandlerModifiable exportItems, FluidTankList importFluids, + FluidTankList exportFluids, int yOffset) { + ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); + builder.widget( + new RecipeProgressWidget(progressSupplier, 78, 23 + yOffset, 20, 20, progressBarTexture, + moveType, recipeMap)); + addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); + addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); + if (specialTexture != null && specialTexturePosition != null) { + addSpecialTexture(builder); + } + return builder; + } + + /** + * This DOES NOT include machine control widgets or binds player inventory + * + * @param progressSupplier a supplier for the progress bar + * @param importItems the input item inventory + * @param exportItems the output item inventory + * @param importFluids the input fluid inventory + * @param exportFluids the output fluid inventory + * @param yOffset the y offset for the gui + * @return the populated builder + */ + public ModularUI.Builder createUITemplateNoOutputs(DoubleSupplier progressSupplier, + IItemHandlerModifiable importItems, + IItemHandlerModifiable exportItems, FluidTankList importFluids, + FluidTankList exportFluids, int yOffset) { + ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); + builder.widget( + new RecipeProgressWidget(progressSupplier, 78, 23 + yOffset, 20, 20, progressBarTexture, + moveType, recipeMap)); + addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); + if (specialTexture != null && specialTexturePosition != null) { + addSpecialTexture(builder); + } + return builder; + } + + /** + * @param builder the builder to add to + * @param itemHandler the item handler to use + * @param fluidHandler the fluid handler to use + * @param isOutputs if slots should be output slots + * @param yOffset the y offset for the gui + */ + protected void addInventorySlotGroup(@NotNull ModularUI.Builder builder, + @NotNull IItemHandlerModifiable itemHandler, + @NotNull FluidTankList fluidHandler, boolean isOutputs, int yOffset) { + int itemInputsCount = itemHandler.getSlots(); + int fluidInputsCount = fluidHandler.getTanks(); + boolean invertFluids = false; + if (itemInputsCount == 0) { + int tmp = itemInputsCount; + itemInputsCount = fluidInputsCount; + fluidInputsCount = tmp; + invertFluids = true; + } + int[] inputSlotGrid = determineSlotsGrid(itemInputsCount); + int itemSlotsToLeft = inputSlotGrid[0]; + int itemSlotsToDown = inputSlotGrid[1]; + int startInputsX = isOutputs ? 106 : 70 - itemSlotsToLeft * 18; + int startInputsY = 33 - (int) (itemSlotsToDown / 2.0 * 18) + yOffset; + boolean wasGroup = itemHandler.getSlots() + fluidHandler.getTanks() == 12; + if (wasGroup) startInputsY -= 9; + else if (itemHandler.getSlots() >= 6 && fluidHandler.getTanks() >= 2 && !isOutputs) startInputsY -= 9; + for (int i = 0; i < itemSlotsToDown; i++) { + for (int j = 0; j < itemSlotsToLeft; j++) { + int slotIndex = i * itemSlotsToLeft + j; + if (slotIndex >= itemInputsCount) break; + int x = startInputsX + 18 * j; + int y = startInputsY + 18 * i; + addSlot(builder, x, y, slotIndex, itemHandler, fluidHandler, invertFluids, isOutputs); + } + } + if (wasGroup) startInputsY += 2; + if (fluidInputsCount > 0 || invertFluids) { + if (itemSlotsToDown >= fluidInputsCount && itemSlotsToLeft < 3) { + int startSpecX = isOutputs ? startInputsX + itemSlotsToLeft * 18 : startInputsX - 18; + for (int i = 0; i < fluidInputsCount; i++) { + int y = startInputsY + 18 * i; + addSlot(builder, startSpecX, y, i, itemHandler, fluidHandler, !invertFluids, isOutputs); + } + } else { + int startSpecY = startInputsY + itemSlotsToDown * 18; + for (int i = 0; i < fluidInputsCount; i++) { + int x = isOutputs ? startInputsX + 18 * (i % 3) : + startInputsX + itemSlotsToLeft * 18 - 18 - 18 * (i % 3); + int y = startSpecY + (i / 3) * 18; + addSlot(builder, x, y, i, itemHandler, fluidHandler, !invertFluids, isOutputs); + } + } + } + } + + /** + * Add a slot to this ui + * + * @param builder the builder to add to + * @param x the x coordinate of the slot + * @param y the y coordinate of the slot + * @param slotIndex the slot index of the slot + * @param itemHandler the item handler to use + * @param fluidHandler the fluid handler to use + * @param isFluid if the slot is a fluid slot + * @param isOutputs if slots should be output slots + */ + protected void addSlot(ModularUI.Builder builder, int x, int y, int slotIndex, IItemHandlerModifiable itemHandler, + FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) { + if (!isFluid) { + builder.widget(new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs).setBackgroundTexture( + getOverlaysForSlot(isOutputs, false, slotIndex == itemHandler.getSlots() - 1))); + } else { + builder.widget(new TankWidget(fluidHandler.getTankAt(slotIndex), x, y, 18, 18).setAlwaysShowFull(true) + .setBackgroundTexture(getOverlaysForSlot(isOutputs, true, slotIndex == fluidHandler.getTanks() - 1)) + .setContainerClicking(true, !isOutputs)); + } + } + + /** + * @param isOutput if the slot is an output slot + * @param isFluid if the slot is a fluid slot + * @param isLast if the slot is the last slot of its type + * @return the overlays for a slot + */ + protected TextureArea[] getOverlaysForSlot(boolean isOutput, boolean isFluid, boolean isLast) { + TextureArea base = isFluid ? GuiTextures.FLUID_SLOT : GuiTextures.SLOT; + byte overlayKey = computeOverlayKey(isOutput, isFluid, isLast); + if (slotOverlays.containsKey(overlayKey)) { + return new TextureArea[] { base, slotOverlays.get(overlayKey) }; + } + return new TextureArea[] { base }; + } + + /** + * @return the height used to determine size of background texture in JEI + */ + public int getPropertyHeightShift() { + int maxPropertyCount = 0; + if (shouldShiftWidgets()) { + for (Recipe recipe : recipeMap.getRecipeList()) { + if (recipe.getPropertyCount() > maxPropertyCount) + maxPropertyCount = recipe.getPropertyCount(); + } + } + return maxPropertyCount * 10; // GTRecipeWrapper#LINE_HEIGHT + } + + /** + * @return widgets should be shifted + */ + private boolean shouldShiftWidgets() { + return recipeMap.getMaxInputs() + recipeMap.getMaxOutputs() >= 6 || + recipeMap.getMaxFluidInputs() + recipeMap.getMaxFluidOutputs() >= 6; + } + + /** + * @return the progress bar's move type + */ + public @NotNull ProgressWidget.MoveType progressBarMoveType() { + return moveType; + } + + /** + * @param moveType the new progress bar move type + */ + public void setProgressBarMoveType(@NotNull ProgressWidget.MoveType moveType) { + this.moveType = moveType; + } + + /** + * @return the texture of the progress bar + */ + public @NotNull TextureArea progressBarTexture() { + return progressBarTexture; + } + + /** + * @param progressBarTexture the new progress bar texture + */ + public void setProgressBarTexture(@NotNull TextureArea progressBarTexture) { + this.progressBarTexture = progressBarTexture; + } + + /** + * @param progressBarTexture the new progress bar texture + * @param moveType the new progress bar move type + */ + public void setProgressBar(@NotNull TextureArea progressBarTexture, @NotNull ProgressWidget.MoveType moveType) { + this.progressBarTexture = progressBarTexture; + this.moveType = moveType; + } + + /** + * @param specialTexture the special texture to set + * @param x the x coordinate of the texture + * @param y the y coordinate of the texture + * @param width the width of the texture + * @param height the height of the texture + */ + public void setSpecialTexture(@NotNull TextureArea specialTexture, int x, int y, int width, int height) { + setSpecialTexture(specialTexture, new int[] { x, y, width, height }); + } + + /** + * @param specialTexture the special texture to set + * @param position the position of the texture: [x, y, width, height] + */ + public void setSpecialTexture(@NotNull TextureArea specialTexture, int @NotNull [] position) { + this.specialTexture = specialTexture; + this.specialTexturePosition = position; + } + + /** + * @return the special texture + */ + public @Nullable TextureArea specialTexture() { + return this.specialTexture; + } + + /** + * @return the special texture's position + */ + public int @Nullable @UnmodifiableView [] specialTexturePosition() { + return this.specialTexturePosition; + } + + /** + * Add a special texture to a builder + * + * @param builder the builder to add to + * @return the updated builder + */ + public @NotNull ModularUI.Builder addSpecialTexture(@NotNull ModularUI.Builder builder) { + if (specialTexturePosition != null) { + builder.image(specialTexturePosition[0], specialTexturePosition[1], + specialTexturePosition[2], + specialTexturePosition[3], specialTexture); + } + return builder; + } + + /** + * @return if this ui should be visible in JEI + */ + public boolean isJEIVisible() { + return isJEIVisible; + } + + /** + * @param isJEIVisible if the ui should be visible in JEI + */ + public void setJEIVisible(boolean isJEIVisible) { + this.isJEIVisible = isJEIVisible; + } + + /** + * @return if item input slot amounts can be modified + */ + public boolean canModifyItemInputs() { + return modifyItemInputs; + } + + /** + * @return if item output slot amounts can be modified + */ + public boolean canModifyItemOutputs() { + return modifyItemOutputs; + } + + /** + * @return if fluid input slot amounts can be modified + */ + public boolean canModifyFluidInputs() { + return modifyFluidInputs; + } + + /** + * @return if fluid output slot amounts can be modified + */ + public boolean canModifyFluidOutputs() { + return modifyFluidOutputs; + } + + /** + * @param texture the texture to set + * @param isOutput if the slot is an output slot + */ + public void setItemSlotOverlay(@NotNull TextureArea texture, boolean isOutput) { + this.slotOverlays.put(computeOverlayKey(isOutput, false, false), texture); + this.slotOverlays.put(computeOverlayKey(isOutput, false, true), texture); + } + + /** + * @param texture the texture to set + * @param isOutput if the slot is an output slot + * @param isLastSlot if the slot is the last slot + */ + public void setItemSlotOverlay(@NotNull TextureArea texture, boolean isOutput, boolean isLastSlot) { + this.slotOverlays.put(computeOverlayKey(isOutput, false, isLastSlot), texture); + } + + /** + * @param texture the texture to set + * @param isOutput if the slot is an output slot + */ + public void setFluidSlotOverlay(@NotNull TextureArea texture, boolean isOutput) { + this.slotOverlays.put(computeOverlayKey(isOutput, true, false), texture); + this.slotOverlays.put(computeOverlayKey(isOutput, true, true), texture); + } + + /** + * @param texture the texture to set + * @param isOutput if the slot is an output slot + * @param isLastSlot if the slot is the last slot + */ + public void setFluidSlotOverlay(@NotNull TextureArea texture, boolean isOutput, boolean isLastSlot) { + this.slotOverlays.put(computeOverlayKey(isOutput, true, isLastSlot), texture); + } + + /** + * @param key the key to store the slot's texture with + * @param texture the texture to store + */ + @ApiStatus.Internal + public void setSlotOverlay(byte key, @NotNull TextureArea texture) { + this.slotOverlays.put(key, texture); + } + + /** + * @return the UI's recipemap + */ + public @NotNull R recipeMap() { + return recipeMap; + } +} diff --git a/src/main/java/gregtech/api/recipes/ui/RecipeMapUIFunction.java b/src/main/java/gregtech/api/recipes/ui/RecipeMapUIFunction.java new file mode 100644 index 00000000000..f654996e31e --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/RecipeMapUIFunction.java @@ -0,0 +1,18 @@ +package gregtech.api.recipes.ui; + +import gregtech.api.recipes.RecipeMap; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface RecipeMapUIFunction { + + /** + * Create a RecipeMapUI using a RecipeMap. + * + * @param recipeMap the recipemap to associate with the ui + * @return the ui + */ + @NotNull + RecipeMapUI apply(@NotNull RecipeMap recipeMap); +} diff --git a/src/main/java/gregtech/api/recipes/ui/impl/AssemblyLineUI.java b/src/main/java/gregtech/api/recipes/ui/impl/AssemblyLineUI.java new file mode 100644 index 00000000000..244bba47b35 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/impl/AssemblyLineUI.java @@ -0,0 +1,72 @@ +package gregtech.api.recipes.ui.impl; + +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.gui.GuiTextures; +import gregtech.api.gui.ModularUI; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUI; + +import net.minecraftforge.items.IItemHandlerModifiable; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class AssemblyLineUI> extends RecipeMapUI { + + /** + * @param recipeMap the recipemap corresponding to this ui + */ + public AssemblyLineUI(@NotNull R recipeMap) { + super(recipeMap, false, false, false, false); + setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, ProgressWidget.MoveType.HORIZONTAL); + } + + @Override + @NotNull + public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, + FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { + ModularUI.Builder builder = ModularUI.builder(GuiTextures.BACKGROUND, 176, 176) + .widget(new ProgressWidget(200, 80, 1, 54, 72, GuiTextures.PROGRESS_BAR_ASSEMBLY_LINE, + ProgressWidget.MoveType.HORIZONTAL)) + .widget(new ProgressWidget(200, 138, 19, 10, 18, GuiTextures.PROGRESS_BAR_ASSEMBLY_LINE_ARROW, + ProgressWidget.MoveType.VERTICAL)); + this.addInventorySlotGroup(builder, importItems, importFluids, false, yOffset); + this.addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); + return builder; + } + + @Override + protected void addInventorySlotGroup(@NotNull ModularUI.Builder builder, + @NotNull IItemHandlerModifiable itemHandler, + @NotNull FluidTankList fluidHandler, boolean isOutputs, int yOffset) { + int startInputsX = 80 - 4 * 18; + int startInputsY = 37 - 2 * 18; + + if (!isOutputs) { + // Data Slot + builder.widget(new SlotWidget(itemHandler, 16, startInputsX + 18 * 7, 1 + 18 * 2, true, true) + .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.DATA_ORB_OVERLAY)); + + // item input slots + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + int slotIndex = i * 4 + j; + addSlot(builder, startInputsX + 18 * j, startInputsY + 18 * i, slotIndex, itemHandler, fluidHandler, + false, false); + } + } + + // fluid slots + int startFluidX = startInputsX + 18 * 5; + for (int i = 0; i < 4; i++) { + addSlot(builder, startFluidX, startInputsY + 18 * i, i, itemHandler, fluidHandler, true, false); + } + } else { + // output slot + addSlot(builder, startInputsX + 18 * 7, 1, 0, itemHandler, fluidHandler, false, true); + } + } +} diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapCokeOven.java b/src/main/java/gregtech/api/recipes/ui/impl/CokeOvenUI.java similarity index 58% rename from src/main/java/gregtech/api/recipes/machines/RecipeMapCokeOven.java rename to src/main/java/gregtech/api/recipes/ui/impl/CokeOvenUI.java index 034ec60c4b9..910d4e4908b 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapCokeOven.java +++ b/src/main/java/gregtech/api/recipes/ui/impl/CokeOvenUI.java @@ -1,22 +1,25 @@ -package gregtech.api.recipes.machines; +package gregtech.api.recipes.ui.impl; import gregtech.api.capability.impl.FluidTankList; import gregtech.api.gui.GuiTextures; import gregtech.api.gui.ModularUI; import gregtech.api.gui.widgets.ProgressWidget; -import gregtech.api.recipes.RecipeBuilder; import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUI; import net.minecraftforge.items.IItemHandlerModifiable; -public class RecipeMapCokeOven> extends RecipeMap { +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; - public RecipeMapCokeOven(String unlocalizedName, int maxInputs, boolean modifyItemInputs, int maxOutputs, - boolean modifyItemOutputs, - int maxFluidInputs, boolean modifyFluidInputs, int maxFluidOutputs, - boolean modifyFluidOutputs, R defaultRecipe, boolean isHidden) { - super(unlocalizedName, maxInputs, modifyItemInputs, maxOutputs, modifyItemOutputs, maxFluidInputs, - modifyFluidInputs, maxFluidOutputs, modifyFluidOutputs, defaultRecipe, isHidden); +@ApiStatus.Internal +public class CokeOvenUI> extends RecipeMapUI { + + /** + * @param recipeMap the recipemap corresponding to this ui + */ + public CokeOvenUI(@NotNull R recipeMap) { + super(recipeMap, false, false, false, false); } @Override diff --git a/src/main/java/gregtech/api/recipes/ui/impl/CrackerUnitUI.java b/src/main/java/gregtech/api/recipes/ui/impl/CrackerUnitUI.java new file mode 100644 index 00000000000..fb4a6698e40 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/impl/CrackerUnitUI.java @@ -0,0 +1,59 @@ +package gregtech.api.recipes.ui.impl; + +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.gui.GuiTextures; +import gregtech.api.gui.ModularUI; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.gui.widgets.RecipeProgressWidget; +import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUI; +import gregtech.api.util.GTUtility; + +import net.minecraftforge.items.IItemHandlerModifiable; + +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.DoubleSupplier; + +@ApiStatus.Internal +public class CrackerUnitUI> extends RecipeMapUI { + + public CrackerUnitUI(@NotNull R recipeMap) { + super(recipeMap, true, true, false, true); + setFluidSlotOverlay(GuiTextures.CRACKING_OVERLAY_1, false); + setFluidSlotOverlay(GuiTextures.CRACKING_OVERLAY_2, true); + setItemSlotOverlay(GuiTextures.CIRCUIT_OVERLAY, false); + setProgressBar(GuiTextures.PROGRESS_BAR_CRACKING, ProgressWidget.MoveType.HORIZONTAL); + } + + @Override + public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, + FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { + ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); + if (recipeMap().getMaxInputs() == 1) { + addSlot(builder, 52, 24 + yOffset, 0, importItems, importFluids, false, false); + } else { + int[] grid = determineSlotsGrid(recipeMap().getMaxInputs()); + for (int y = 0; y < grid[1]; y++) { + for (int x = 0; x < grid[0]; x++) { + addSlot(builder, 34 + (x * 18) - (Math.max(0, grid[0] - 2) * 18), + 24 + (y * 18) - (Math.max(0, grid[1] - 1) * 18) + yOffset, + y * grid[0] + x, importItems, importFluids, false, false); + } + } + } + + addInventorySlotGroup(builder, exportItems, exportFluids, true, yOffset); + addSlot(builder, 52, 24 + yOffset + 19 + 18, 0, importItems, importFluids, true, false); + addSlot(builder, 34, 24 + yOffset + 19 + 18, 1, importItems, importFluids, true, false); + + Pair suppliers = GTUtility.createPairedSupplier(200, 41, 0.5); + builder.widget(new RecipeProgressWidget(suppliers.getLeft(), 42, 24 + yOffset + 18, 21, 19, + GuiTextures.PROGRESS_BAR_CRACKING_INPUT, ProgressWidget.MoveType.VERTICAL, recipeMap())); + builder.widget(new RecipeProgressWidget(suppliers.getRight(), 78, 23 + yOffset, 20, 20, progressBarTexture(), + progressBarMoveType(), recipeMap())); + return builder; + } +} diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapDistillationTower.java b/src/main/java/gregtech/api/recipes/ui/impl/DistillationTowerUI.java similarity index 75% rename from src/main/java/gregtech/api/recipes/machines/RecipeMapDistillationTower.java rename to src/main/java/gregtech/api/recipes/ui/impl/DistillationTowerUI.java index d338f8c5898..f87d1767ac1 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapDistillationTower.java +++ b/src/main/java/gregtech/api/recipes/ui/impl/DistillationTowerUI.java @@ -1,4 +1,4 @@ -package gregtech.api.recipes.machines; +package gregtech.api.recipes.ui.impl; import gregtech.api.capability.impl.FluidTankList; import gregtech.api.gui.GuiTextures; @@ -8,19 +8,16 @@ import gregtech.api.gui.widgets.SlotWidget; import gregtech.api.gui.widgets.TankWidget; import gregtech.api.recipes.RecipeMap; -import gregtech.api.recipes.builders.UniversalDistillationRecipeBuilder; +import gregtech.api.recipes.ui.RecipeMapUI; import net.minecraftforge.items.IItemHandlerModifiable; -public class RecipeMapDistillationTower extends RecipeMap { +import org.jetbrains.annotations.NotNull; - public RecipeMapDistillationTower(String unlocalizedName, int maxInputs, boolean modifyItemInputs, int maxOutputs, - boolean modifyItemOutputs, - int maxFluidInputs, boolean modifyFluidInputs, int maxFluidOutputs, - boolean modifyFluidOutputs, UniversalDistillationRecipeBuilder defaultRecipe, - boolean isHidden) { - super(unlocalizedName, maxInputs, modifyItemInputs, maxOutputs, modifyItemOutputs, maxFluidInputs, - modifyFluidInputs, maxFluidOutputs, modifyFluidOutputs, defaultRecipe, isHidden); +public class DistillationTowerUI> extends RecipeMapUI { + + public DistillationTowerUI(@NotNull R recipeMap) { + super(recipeMap, true, true, true, false); } @Override @@ -52,7 +49,6 @@ else if (slotIndex == 2 || slotIndex == 5 || slotIndex == 8 || slotIndex == 11) } @Override - // this DOES NOT include machine control widgets or binds player inventory public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { ModularUI.Builder builder = ModularUI.defaultBuilder(yOffset); @@ -60,14 +56,16 @@ public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, ProgressWidget.MoveType.HORIZONTAL)); addInventorySlotGroup(builder, importItems, importFluids, false, 9); addInventorySlotGroup(builder, exportItems, exportFluids, true, 9); - if (this.specialTexture != null && this.specialTexturePosition != null) + if (specialTexture() != null && specialTexturePosition() != null) { addSpecialTexture(builder); + } return builder; } @Override - protected void addInventorySlotGroup(ModularUI.Builder builder, IItemHandlerModifiable itemHandler, - FluidTankList fluidHandler, boolean isOutputs, int yOffset) { + protected void addInventorySlotGroup(@NotNull ModularUI.Builder builder, + @NotNull IItemHandlerModifiable itemHandler, + @NotNull FluidTankList fluidHandler, boolean isOutputs, int yOffset) { int itemInputsCount = itemHandler.getSlots(); int fluidInputsCount = fluidHandler.getTanks(); boolean invertFluids = false; @@ -77,7 +75,7 @@ protected void addInventorySlotGroup(ModularUI.Builder builder, IItemHandlerModi fluidInputsCount = tmp; invertFluids = true; } - int[] inputSlotGrid = determineSlotsGrid(itemInputsCount); + int[] inputSlotGrid = RecipeMapUI.determineSlotsGrid(itemInputsCount); int itemSlotsToLeft = inputSlotGrid[0]; int itemSlotsToDown = inputSlotGrid[1]; int startInputsX = isOutputs ? 104 : 68 - itemSlotsToLeft * 18; @@ -85,17 +83,17 @@ protected void addInventorySlotGroup(ModularUI.Builder builder, IItemHandlerModi boolean wasGroupOutput = itemHandler.getSlots() + fluidHandler.getTanks() == 12; if (wasGroupOutput && isOutputs) startInputsY -= 9; if (itemHandler.getSlots() == 6 && fluidHandler.getTanks() == 2 && !isOutputs) startInputsY -= 9; - if (!isOutputs) + if (!isOutputs) { addSlot(builder, 40, startInputsY + (itemSlotsToDown - 1) * 18 - 18, 0, itemHandler, fluidHandler, invertFluids, false); - else + } else { addSlot(builder, 94, startInputsY + (itemSlotsToDown - 1) * 18, 0, itemHandler, fluidHandler, invertFluids, true); + } if (wasGroupOutput) startInputsY += 2; - if (!isOutputs) - return; + if (!isOutputs) return; if (!invertFluids) { startInputsY -= 18; @@ -103,7 +101,6 @@ protected void addInventorySlotGroup(ModularUI.Builder builder, IItemHandlerModi } if (fluidInputsCount > 0 || invertFluids) { - int startSpecY = startInputsY + itemSlotsToDown * 18; for (int i = 0; i < fluidInputsCount; i++) { int x = startInputsX + 18 * (i % 3); diff --git a/src/main/java/gregtech/api/recipes/ui/impl/FormingPressUI.java b/src/main/java/gregtech/api/recipes/ui/impl/FormingPressUI.java new file mode 100644 index 00000000000..7d63ff00da7 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/impl/FormingPressUI.java @@ -0,0 +1,41 @@ +package gregtech.api.recipes.ui.impl; + +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.gui.GuiTextures; +import gregtech.api.gui.ModularUI; +import gregtech.api.gui.resources.TextureArea; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUI; + +import net.minecraftforge.items.IItemHandlerModifiable; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public class FormingPressUI> extends RecipeMapUI { + + public FormingPressUI(@NotNull R recipeMap) { + super(recipeMap, true, true, true, true); + setProgressBar(GuiTextures.PROGRESS_BAR_COMPRESS, ProgressWidget.MoveType.HORIZONTAL); + } + + @Override + protected void addSlot(ModularUI.Builder builder, int x, int y, int slotIndex, IItemHandlerModifiable itemHandler, + FluidTankList fluidHandler, boolean isFluid, boolean isOutputs) { + SlotWidget slotWidget = new SlotWidget(itemHandler, slotIndex, x, y, true, !isOutputs); + TextureArea base = GuiTextures.SLOT; + if (isOutputs) + slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_3); + else if (slotIndex == 0 || slotIndex == 3) + slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_2); + else if (slotIndex == 1 || slotIndex == 4) + slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_4); + else if (slotIndex == 2 || slotIndex == 5) + slotWidget.setBackgroundTexture(base, GuiTextures.PRESS_OVERLAY_1); + + builder.widget(slotWidget); + } +} diff --git a/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java b/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java new file mode 100644 index 00000000000..917fc953dab --- /dev/null +++ b/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java @@ -0,0 +1,49 @@ +package gregtech.api.recipes.ui.impl; + +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.gui.GuiTextures; +import gregtech.api.gui.ModularUI; +import gregtech.api.gui.widgets.ImageWidget; +import gregtech.api.gui.widgets.ProgressWidget; +import gregtech.api.gui.widgets.SlotWidget; +import gregtech.api.recipes.RecipeMap; +import gregtech.api.recipes.ui.RecipeMapUI; +import gregtech.api.util.GTUtility; + +import net.minecraftforge.items.IItemHandlerModifiable; + +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.DoubleSupplier; + +@ApiStatus.Internal +public class ResearchStationUI> extends RecipeMapUI { + + public ResearchStationUI(@NotNull R recipeMap) { + super(recipeMap, true, true, true, true); + setItemSlotOverlay(GuiTextures.SCANNER_OVERLAY, false); + setItemSlotOverlay(GuiTextures.RESEARCH_STATION_OVERLAY, true); + setProgressBar(GuiTextures.PROGRESS_BAR_ARROW, ProgressWidget.MoveType.HORIZONTAL); + } + + @Override + @NotNull + public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, IItemHandlerModifiable exportItems, + FluidTankList importFluids, FluidTankList exportFluids, int yOffset) { + Pair pairedSuppliers = GTUtility.createPairedSupplier(200, 90, 0.75); + return ModularUI.builder(GuiTextures.BACKGROUND, 176, 166) + .widget(new ImageWidget(10, 0, 84, 60, GuiTextures.PROGRESS_BAR_RESEARCH_STATION_BASE)) + .widget(new ProgressWidget(pairedSuppliers.getLeft(), 72, 28, 54, 5, + GuiTextures.PROGRESS_BAR_RESEARCH_STATION_1, ProgressWidget.MoveType.HORIZONTAL)) + .widget(new ProgressWidget(pairedSuppliers.getRight(), 119, 32, 10, 18, + GuiTextures.PROGRESS_BAR_RESEARCH_STATION_2, ProgressWidget.MoveType.VERTICAL_DOWNWARDS)) + .widget(new SlotWidget(importItems, 0, 115, 50, true, true) + .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.DATA_ORB_OVERLAY)) + .widget(new SlotWidget(importItems, 1, 43, 21, true, true) + .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.SCANNER_OVERLAY)) + .widget(new SlotWidget(exportItems, 0, 97, 21, true, true) + .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.RESEARCH_STATION_OVERLAY)); + } +} diff --git a/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java b/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java index 37c745dc5c5..8d4af1f4fa7 100644 --- a/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java +++ b/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java @@ -42,6 +42,7 @@ public static void register() { Americium = new Material.Builder(3, gregtechId("americium")) .ingot(3) .liquid(new FluidBuilder().temperature(1449)) + .plasma() .color(0x287869).iconSet(METALLIC) .flags(EXT_METAL, GENERATE_FOIL, GENERATE_FINE_WIRE, GENERATE_DOUBLE_PLATE) .element(Elements.Am) @@ -814,6 +815,7 @@ public static void register() { Tin = new Material.Builder(112, gregtechId("tin")) .ingot(1) .liquid(new FluidBuilder().temperature(505)) + .plasma() .ore() .color(0xDCDCDC) .flags(EXT2_METAL, MORTAR_GRINDABLE, GENERATE_ROTOR, GENERATE_SPRING, GENERATE_SPRING_SMALL, diff --git a/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java b/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java index f47c7cc0a42..de2bdf4a703 100644 --- a/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java +++ b/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java @@ -194,7 +194,6 @@ public static void register() { Chloromethane = new Material.Builder(1024, gregtechId("chloromethane")) .gas() .color(0xC82CA0) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 1, Hydrogen, 3, Chlorine, 1) .build(); @@ -221,12 +220,14 @@ public static void register() { Propene = new Material.Builder(1028, gregtechId("propene")) .gas() .color(0xFFDD55) + .flags(DISABLE_DECOMPOSITION) .components(Carbon, 3, Hydrogen, 6) .build(); Ethane = new Material.Builder(1029, gregtechId("ethane")) .gas() .color(0xC8C8FF) + .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 6) .build(); @@ -268,7 +269,6 @@ public static void register() { Ethenone = new Material.Builder(1035, gregtechId("ethenone")) .fluid() .color(0x141446) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 2, Oxygen, 1) .build(); @@ -331,7 +331,6 @@ public static void register() { AceticAcid = new Material.Builder(1044, gregtechId("acetic_acid")) .liquid(new FluidBuilder().attribute(FluidAttributes.ACID)) .color(0xC8B4A0) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 4, Oxygen, 2) .build(); @@ -373,7 +372,6 @@ public static void register() { Acetone = new Material.Builder(1050, gregtechId("acetone")) .fluid() .color(0xAFAFAF) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 3, Hydrogen, 6, Oxygen, 1) .build(); diff --git a/src/main/java/gregtech/api/unification/ore/OrePrefix.java b/src/main/java/gregtech/api/unification/ore/OrePrefix.java index 64eb11cd7fa..30239988324 100644 --- a/src/main/java/gregtech/api/unification/ore/OrePrefix.java +++ b/src/main/java/gregtech/api/unification/ore/OrePrefix.java @@ -467,6 +467,7 @@ public static void init() { block.modifyMaterialAmount(Materials.Glowstone, 4); block.modifyMaterialAmount(Materials.NetherQuartz, 4); + block.modifyMaterialAmount(Materials.CertusQuartz, 4); block.modifyMaterialAmount(Materials.Brick, 4); block.modifyMaterialAmount(Materials.Clay, 4); block.modifyMaterialAmount(Materials.Glass, 1); diff --git a/src/main/java/gregtech/api/util/AssemblyLineManager.java b/src/main/java/gregtech/api/util/AssemblyLineManager.java index f4f577996b2..a2652acfa84 100644 --- a/src/main/java/gregtech/api/util/AssemblyLineManager.java +++ b/src/main/java/gregtech/api/util/AssemblyLineManager.java @@ -121,33 +121,56 @@ public static void createDefaultResearchRecipe(@NotNull AssemblyLineRecipeBuilde for (AssemblyLineRecipeBuilder.ResearchRecipeEntry entry : builder.getRecipeEntries()) { createDefaultResearchRecipe(entry.getResearchId(), entry.getResearchStack(), entry.getDataStack(), + entry.getIgnoreNBT(), entry.getDuration(), entry.getEUt(), entry.getCWUt()); } } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public static void createDefaultResearchRecipe(@NotNull String researchId, @NotNull ItemStack researchItem, @NotNull ItemStack dataItem, int duration, int EUt, int CWUt) { + createDefaultResearchRecipe(researchId, researchItem, dataItem, true, duration, EUt, CWUt); + } + + public static void createDefaultResearchRecipe(@NotNull String researchId, @NotNull ItemStack researchItem, + @NotNull ItemStack dataItem, boolean ignoreNBT, int duration, + int EUt, int CWUt) { if (!ConfigHolder.machines.enableResearch) return; NBTTagCompound compound = GTUtility.getOrCreateNbtCompound(dataItem); writeResearchToNBT(compound, researchId); if (CWUt > 0) { - RecipeMaps.RESEARCH_STATION_RECIPES.recipeBuilder() + RecipeBuilder researchBuilder = RecipeMaps.RESEARCH_STATION_RECIPES.recipeBuilder() .inputNBT(dataItem.getItem(), 1, dataItem.getMetadata(), NBTMatcher.ANY, NBTCondition.ANY) - .inputs(researchItem) .outputs(dataItem) .EUt(EUt) .CWUt(CWUt) - .totalCWU(duration) - .buildAndRegister(); + .totalCWU(duration); + + if (ignoreNBT) { + researchBuilder.inputNBT(researchItem.getItem(), 1, researchItem.getMetadata(), NBTMatcher.ANY, + NBTCondition.ANY); + } else { + researchBuilder.inputs(researchItem); + } + + researchBuilder.buildAndRegister(); } else { RecipeBuilder builder = RecipeMaps.SCANNER_RECIPES.recipeBuilder() .inputNBT(dataItem.getItem(), 1, dataItem.getMetadata(), NBTMatcher.ANY, NBTCondition.ANY) - .inputs(researchItem) .outputs(dataItem) .duration(duration) .EUt(EUt); + + if (ignoreNBT) { + builder.inputNBT(researchItem.getItem(), 1, researchItem.getMetadata(), NBTMatcher.ANY, + NBTCondition.ANY); + } else { + builder.inputs(researchItem); + } + builder.applyProperty(ScanProperty.getInstance(), true); builder.buildAndRegister(); } diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java index 23512da30cc..b7da571251d 100644 --- a/src/main/java/gregtech/api/util/GTUtility.java +++ b/src/main/java/gregtech/api/util/GTUtility.java @@ -7,6 +7,7 @@ import gregtech.api.cover.CoverDefinition; import gregtech.api.damagesources.DamageSources; import gregtech.api.fluids.GTFluid; +import gregtech.api.gui.widgets.ProgressWidget; import gregtech.api.items.behavior.CoverItemBehavior; import gregtech.api.items.metaitem.MetaItem; import gregtech.api.items.metaitem.stats.IItemBehaviour; @@ -57,15 +58,22 @@ import net.minecraftforge.items.IItemHandlerModifiable; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.AtomicDouble; import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.AbstractList; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; import java.util.function.BooleanSupplier; +import java.util.function.DoubleSupplier; import java.util.function.Function; import java.util.function.Predicate; @@ -913,4 +921,21 @@ public static TextComponentTranslation getFluidTranslation(@Nullable Fluid fluid } return new TextComponentTranslation(fluid.getUnlocalizedName()); } + + public static @NotNull Pair createPairedSupplier(int ticksPerCycle, int width, + double splitPoint) { + AtomicDouble tracker = new AtomicDouble(0.0); + DoubleSupplier supplier1 = new ProgressWidget.TimedProgressSupplier(ticksPerCycle, width, false) { + + @Override + public double getAsDouble() { + double val = super.getAsDouble(); + tracker.set(val); + return val >= splitPoint ? 1.0 : (1.0 / splitPoint) * val; + } + }; + DoubleSupplier supplier2 = () -> tracker.get() >= splitPoint ? + (1.0 / (1 - splitPoint)) * (tracker.get() - splitPoint) : 0; + return Pair.of(supplier1, supplier2); + } } diff --git a/src/main/java/gregtech/api/util/oreglob/OreGlob.java b/src/main/java/gregtech/api/util/oreglob/OreGlob.java index 072e4959eeb..a560fcae1bf 100644 --- a/src/main/java/gregtech/api/util/oreglob/OreGlob.java +++ b/src/main/java/gregtech/api/util/oreglob/OreGlob.java @@ -7,21 +7,19 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; -import java.util.Set; -import java.util.function.Function; /** * Glob-like string matcher language designed for ore dictionary matching. *

- * An OreGlob instance provides two functions: the ability to match strings, - * and the ability to translate expression structure into user-friendly text - * explanations. The text can be either a plaintext, or a text formatted by standard + * An OreGlob instance provides two functions: the ability to match strings, and the ability to translate expression + * structure into user-friendly text explanations. The text can be either a plaintext, or a text formatted by standard * Minecraft text format. */ public abstract class OreGlob { - private static Function compiler; + private static OreGlobCompiler compiler; /** * Tries to compile the string expression into OreGlob instance. @@ -29,15 +27,31 @@ public abstract class OreGlob { * @param expression OreGlob expression * @return Compilation result * @throws IllegalStateException If compiler is not provided yet + * @deprecated use {@link #compile(String, boolean)} */ @NotNull + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public static OreGlobCompileResult compile(@NotNull String expression) { + return compile(expression, true); + } + + /** + * Tries to compile the string expression into OreGlob instance. + * + * @param expression OreGlob expression + * @param ignoreCase Whether the resulting OreGlob instance should do case-insensitive matches + * @return Compilation result + * @throws IllegalStateException If compiler is not provided yet + */ + @NotNull + public static OreGlobCompileResult compile(@NotNull String expression, boolean ignoreCase) { if (compiler == null) throw new IllegalStateException("Compiler unavailable"); - return compiler.apply(expression); + return compiler.compile(expression, ignoreCase); } @ApiStatus.Internal - public static void setCompiler(@NotNull Function compiler) { + public static void setCompiler(@NotNull OreGlobCompiler compiler) { OreGlob.compiler = compiler; } @@ -60,30 +74,97 @@ public static void setCompiler(@NotNull Function c public abstract boolean matches(@NotNull String input); /** - * Tries to match each ore dictionary entries associated with given item. - * If any of them matches, {@code true} is returned. *

- * For items not associated with any ore dictionary entries, this method returns - * {@code true} if this instance matches empty string instead. + * Tries to match each ore dictionary entries associated with given item. If any of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

* * @param stack Item input * @return Whether this instance matches the input + * @deprecated use {@link #matchesAll(ItemStack)} or {@link #matchesAny(ItemStack)} */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public final boolean matches(@NotNull ItemStack stack) { - Set oreDicts = OreDictUnifier.getOreDictionaryNames(stack); - if (oreDicts.isEmpty()) { - return matches(""); - } else { - for (String oreDict : oreDicts) { - if (matches(oreDict)) return true; - } - return false; - } + return matchesAny(stack); + } + + /** + *

+ * Tries to match each ore dictionary entries associated with given item. If any of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

+ * + * @param stack Item input + * @return Whether this instance matches the input + */ + public final boolean matchesAny(@NotNull ItemStack stack) { + return matchesAny(OreDictUnifier.getOreDictionaryNames(stack), true); + } + + /** + *

+ * Tries to match each ore dictionary entries associated with given item. If all of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

+ * + * @param stack Item input + * @return Whether this instance matches the input + */ + public final boolean matchesAll(@NotNull ItemStack stack) { + return matchesAll(OreDictUnifier.getOreDictionaryNames(stack), true); + } + + /** + *

+ * Tries to match each input. If any of them matches, {@code true} is returned. + *

+ * + * @param inputs Collection of input strings + * @param specialEmptyMatch If {@code true}, this method will match an empty string ({@code ""}) if the input + * collection is empty. If {@code true}, this method will return {@code false} in such + * scenario. + * @return Whether this instance matches the input + */ + public final boolean matchesAny(@NotNull Collection inputs, boolean specialEmptyMatch) { + if (specialEmptyMatch && inputs.isEmpty()) return matches(""); + for (String input : inputs) if (matches(input)) return true; + return false; + } + + /** + *

+ * Tries to match each input. If all of them matches, {@code true} is returned. Note that this method does not have + * special case for empty inputs. + *

+ * + * @param inputs Collection of input strings + * @param specialEmptyMatch If {@code true}, this method will match an empty string ({@code ""}) if the input + * collection is empty. If {@code true}, this method will return {@code true} in such + * scenario. + * @return Whether this instance matches the input + */ + public final boolean matchesAll(@NotNull Collection inputs, boolean specialEmptyMatch) { + if (specialEmptyMatch && inputs.isEmpty()) return matches(""); + for (String input : inputs) if (!matches(input)) return false; + return true; } /** - * Visualize this instance with standard Minecraft text formatting. Two spaces (' ') will - * be used as indentation. + * Visualize this instance with standard Minecraft text formatting. Two spaces ({@code ' '}) will be used as + * indentation. * * @return Formatted visualization * @see OreGlob#toFormattedString(String) diff --git a/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java b/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java new file mode 100644 index 00000000000..d66fb7785a0 --- /dev/null +++ b/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java @@ -0,0 +1,10 @@ +package gregtech.api.util.oreglob; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface OreGlobCompiler { + + @NotNull + OreGlobCompileResult compile(@NotNull String expression, boolean ignoreCase); +} diff --git a/src/main/java/gregtech/client/particle/VanillaParticleEffects.java b/src/main/java/gregtech/client/particle/VanillaParticleEffects.java index 2765d3945d9..ddfa0592369 100644 --- a/src/main/java/gregtech/client/particle/VanillaParticleEffects.java +++ b/src/main/java/gregtech/client/particle/VanillaParticleEffects.java @@ -26,35 +26,6 @@ public enum VanillaParticleEffects implements IMachineParticleEffect { mte.getWorld().spawnParticle(EnumParticleTypes.SMOKE_NORMAL, x, y, z, 0, 0, 0); }), - MUFFLER_SMOKE(mte -> { - if (mte.getWorld() == null || mte.getPos() == null) return; - - BlockPos pos = mte.getPos(); - EnumFacing facing = mte.getFrontFacing(); - float xPos = facing.getXOffset() * 0.76F + pos.getX() + 0.25F; - float yPos = facing.getYOffset() * 0.76F + pos.getY() + 0.25F; - float zPos = facing.getZOffset() * 0.76F + pos.getZ() + 0.25F; - - float ySpd = facing.getYOffset() * 0.1F + 0.2F + 0.1F * GTValues.RNG.nextFloat(); - float xSpd; - float zSpd; - - if (facing.getYOffset() == -1) { - float temp = GTValues.RNG.nextFloat() * 2 * (float) Math.PI; - xSpd = (float) Math.sin(temp) * 0.1F; - zSpd = (float) Math.cos(temp) * 0.1F; - } else { - xSpd = facing.getXOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); - zSpd = facing.getZOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); - } - - xPos += GTValues.RNG.nextFloat() * 0.5F; - yPos += GTValues.RNG.nextFloat() * 0.5F; - zPos += GTValues.RNG.nextFloat() * 0.5F; - - mte.getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, xPos, yPos, zPos, xSpd, ySpd, zSpd); - }), - PBF_SMOKE(mte -> { if (mte.getWorld() == null || mte.getPos() == null) return; @@ -167,12 +138,12 @@ public void runEffect(@NotNull MetaTileEntity metaTileEntity) { } @SideOnly(Side.CLIENT) - public static void defaultFrontEffect(MetaTileEntity mte, EnumParticleTypes... particles) { + public static void defaultFrontEffect(@NotNull MetaTileEntity mte, EnumParticleTypes... particles) { defaultFrontEffect(mte, 0.0F, particles); } @SideOnly(Side.CLIENT) - public static void defaultFrontEffect(MetaTileEntity mte, float yOffset, EnumParticleTypes... particles) { + public static void defaultFrontEffect(@NotNull MetaTileEntity mte, float yOffset, EnumParticleTypes... particles) { if (particles == null || particles.length == 0) return; if (mte.getWorld() == null || mte.getPos() == null) return; @@ -201,4 +172,34 @@ public static void defaultFrontEffect(MetaTileEntity mte, float yOffset, EnumPar mte.getWorld().spawnParticle(particle, x, y, z, 0, 0, 0); } } + + @SideOnly(Side.CLIENT) + public static void mufflerEffect(@NotNull MetaTileEntity mte, @NotNull EnumParticleTypes particle) { + if (mte.getWorld() == null || mte.getPos() == null) return; + + BlockPos pos = mte.getPos(); + EnumFacing facing = mte.getFrontFacing(); + float xPos = facing.getXOffset() * 0.76F + pos.getX() + 0.25F; + float yPos = facing.getYOffset() * 0.76F + pos.getY() + 0.25F; + float zPos = facing.getZOffset() * 0.76F + pos.getZ() + 0.25F; + + float ySpd = facing.getYOffset() * 0.1F + 0.2F + 0.1F * GTValues.RNG.nextFloat(); + float xSpd; + float zSpd; + + if (facing.getYOffset() == -1) { + float temp = GTValues.RNG.nextFloat() * 2 * (float) Math.PI; + xSpd = (float) Math.sin(temp) * 0.1F; + zSpd = (float) Math.cos(temp) * 0.1F; + } else { + xSpd = facing.getXOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); + zSpd = facing.getZOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); + } + + xPos += GTValues.RNG.nextFloat() * 0.5F; + yPos += GTValues.RNG.nextFloat() * 0.5F; + zPos += GTValues.RNG.nextFloat() * 0.5F; + + mte.getWorld().spawnParticle(particle, xPos, yPos, zPos, xSpd, ySpd, zSpd); + } } diff --git a/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java b/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java index 841d230d0f5..632bdad8f2b 100644 --- a/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java +++ b/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java @@ -100,7 +100,7 @@ public static void initializeRestrictor(TextureMap map) { static { FACE_BORDER_MAP.put(EnumFacing.DOWN, - borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.EAST, EnumFacing.WEST)); + borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST)); FACE_BORDER_MAP.put(EnumFacing.UP, borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST)); FACE_BORDER_MAP.put(EnumFacing.NORTH, diff --git a/src/main/java/gregtech/client/renderer/texture/Textures.java b/src/main/java/gregtech/client/renderer/texture/Textures.java index 3f2eaabe847..a44606ddf6f 100644 --- a/src/main/java/gregtech/client/renderer/texture/Textures.java +++ b/src/main/java/gregtech/client/renderer/texture/Textures.java @@ -7,8 +7,19 @@ import gregtech.client.renderer.CubeRendererState; import gregtech.client.renderer.ICubeRenderer; import gregtech.client.renderer.cclop.UVMirror; -import gregtech.client.renderer.texture.cube.*; -import gregtech.client.renderer.texture.custom.*; +import gregtech.client.renderer.texture.cube.AlignedOrientedOverlayRenderer; +import gregtech.client.renderer.texture.cube.LDPipeOverlayRenderer; +import gregtech.client.renderer.texture.cube.OrientedOverlayRenderer; +import gregtech.client.renderer.texture.cube.SidedCubeRenderer; +import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer; +import gregtech.client.renderer.texture.cube.SimpleOverlayRenderer; +import gregtech.client.renderer.texture.cube.SimpleSidedCubeRenderer; +import gregtech.client.renderer.texture.custom.ClipboardRenderer; +import gregtech.client.renderer.texture.custom.CrateRenderer; +import gregtech.client.renderer.texture.custom.DrumRenderer; +import gregtech.client.renderer.texture.custom.FireboxActiveRenderer; +import gregtech.client.renderer.texture.custom.LargeTurbineRenderer; +import gregtech.client.renderer.texture.custom.QuantumStorageRenderer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; @@ -49,7 +60,6 @@ public class Textures { public static final CrateRenderer METAL_CRATE = new CrateRenderer("storage/crates/metal_crate"); public static final DrumRenderer WOODEN_DRUM = new DrumRenderer("storage/drums/wooden_drum"); public static final DrumRenderer DRUM = new DrumRenderer("storage/drums/drum"); - public static final SafeRenderer SAFE = new SafeRenderer("storage/safe"); public static final LargeTurbineRenderer LARGE_TURBINE_ROTOR_RENDERER = new LargeTurbineRenderer(); public static final QuantumStorageRenderer QUANTUM_STORAGE_RENDERER = new QuantumStorageRenderer(); diff --git a/src/main/java/gregtech/client/renderer/texture/custom/SafeRenderer.java b/src/main/java/gregtech/client/renderer/texture/custom/SafeRenderer.java deleted file mode 100644 index 9e213bfa27d..00000000000 --- a/src/main/java/gregtech/client/renderer/texture/custom/SafeRenderer.java +++ /dev/null @@ -1,94 +0,0 @@ -package gregtech.client.renderer.texture.custom; - -import gregtech.api.GTValues; -import gregtech.client.renderer.texture.Textures; - -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.renderer.texture.TextureMap; -import net.minecraft.util.BlockRenderLayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumFacing.Axis; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -import codechicken.lib.render.CCRenderState; -import codechicken.lib.render.pipeline.IVertexOperation; -import codechicken.lib.texture.TextureUtils.IIconRegister; -import codechicken.lib.vec.Cuboid6; -import codechicken.lib.vec.Matrix4; -import codechicken.lib.vec.Rotation; - -import java.util.Arrays; -import java.util.List; - -public class SafeRenderer implements IIconRegister { - - private static final Cuboid6 mainBoxOuter = new Cuboid6(3 / 16.0, 0 / 16.0, 3 / 16.0, 13 / 16.0, 14 / 16.0, - 13 / 16.0); - private static final Cuboid6 mainBoxInner = new Cuboid6(4 / 16.0, 1 / 16.0, 3 / 16.0, 12 / 16.0, 13 / 16.0, - 12 / 16.0); - private static final Cuboid6 doorBox = new Cuboid6(4 / 16.0, 1 / 16.0, 3 / 16.0, 12 / 16.0, 13 / 16.0, 4 / 16.0); - private static final List rotations = Arrays.asList(EnumFacing.NORTH, EnumFacing.WEST, EnumFacing.SOUTH, - EnumFacing.EAST); - - private final String basePath; - - @SideOnly(Side.CLIENT) - private TextureAtlasSprite[] textures; - - public SafeRenderer(String basePath) { - this.basePath = basePath; - Textures.iconRegisters.add(this); - } - - @SideOnly(Side.CLIENT) - public TextureAtlasSprite getParticleTexture() { - return textures[1]; - } - - @Override - @SideOnly(Side.CLIENT) - public void registerIcons(TextureMap textureMap) { - String formattedBase = GTValues.MODID + ":blocks/" + basePath; - this.textures = new TextureAtlasSprite[7]; - this.textures[0] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/base_bottom")); - this.textures[1] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/base_top")); - this.textures[2] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/base_side")); - this.textures[3] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/base_front")); - - this.textures[4] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/door_side")); - this.textures[5] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/door_back")); - this.textures[6] = textureMap.registerSprite(new ResourceLocation(formattedBase + "/door_front")); - } - - @SideOnly(Side.CLIENT) - public void render(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline, EnumFacing rotation, - float capRotation) { - translation.translate(0.5, 0.5, 0.5); - translation.rotate(Math.toRadians(90.0 * rotations.indexOf(rotation)), Rotation.axes[1]); - translation.translate(-0.5, -0.5, -0.5); - - for (EnumFacing renderSide : EnumFacing.VALUES) { - TextureAtlasSprite baseSprite = renderSide.getAxis() == Axis.Y ? - textures[renderSide.getIndex()] : - renderSide == EnumFacing.NORTH ? textures[3] : textures[2]; - Textures.renderFace(renderState, translation, pipeline, renderSide, mainBoxOuter, baseSprite, - BlockRenderLayer.CUTOUT_MIPPED); - if (renderSide == EnumFacing.NORTH) continue; - Textures.renderFace(renderState, translation, pipeline, renderSide, mainBoxInner, baseSprite, - BlockRenderLayer.CUTOUT_MIPPED); - } - - translation.translate(4 / 16.0, 7 / 16.0, 3 / 16.0); - translation.rotate(Math.toRadians(capRotation), Rotation.axes[1]); - translation.translate(-4 / 16.0, -7 / 16.0, -3 / 16.0); - - for (EnumFacing renderSide : EnumFacing.VALUES) { - TextureAtlasSprite doorSprite = renderSide == EnumFacing.NORTH ? textures[6] : - renderSide == EnumFacing.SOUTH ? textures[5] : textures[4]; - Textures.renderFace(renderState, translation, pipeline, renderSide, doorBox, doorSprite, - BlockRenderLayer.CUTOUT_MIPPED); - } - } -} diff --git a/src/main/java/gregtech/common/CommonProxy.java b/src/main/java/gregtech/common/CommonProxy.java index 7cac0f93204..f8a956e201b 100644 --- a/src/main/java/gregtech/common/CommonProxy.java +++ b/src/main/java/gregtech/common/CommonProxy.java @@ -297,9 +297,9 @@ public static void initComponents(RegistryEvent.Register event) { @SubscribeEvent public static void registerRecipes(RegistryEvent.Register event) { // Registers Fusion tiers for the FusionEUToStartProperty - FusionEUToStartProperty.registerFusionTier(6, "(MK1)"); - FusionEUToStartProperty.registerFusionTier(7, "(MK2)"); - FusionEUToStartProperty.registerFusionTier(8, "(MK3)"); + FusionEUToStartProperty.registerFusionTier(GTValues.LuV, "(MK1)"); + FusionEUToStartProperty.registerFusionTier(GTValues.ZPM, "(MK2)"); + FusionEUToStartProperty.registerFusionTier(GTValues.UV, "(MK3)"); // Register data stick copying custom scanner logic AssemblyLineManager.registerScannerLogic(); diff --git a/src/main/java/gregtech/common/covers/CoverFluidFilter.java b/src/main/java/gregtech/common/covers/CoverFluidFilter.java index c746215c983..cd5d06ee5a4 100644 --- a/src/main/java/gregtech/common/covers/CoverFluidFilter.java +++ b/src/main/java/gregtech/common/covers/CoverFluidFilter.java @@ -110,13 +110,15 @@ public void renderCover(@NotNull CCRenderState renderState, @NotNull Matrix4 tra } @Override - public T getCapability(@NotNull Capability capability, T defaultValue) { + public T getCapability(@NotNull Capability capability, @Nullable T defaultValue) { if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { - IFluidHandler delegate = (IFluidHandler) defaultValue; - if (fluidHandler == null || fluidHandler.delegate != delegate) { - this.fluidHandler = new FluidHandlerFiltered(delegate); + if (defaultValue instanceof IFluidHandler delegate) { + if (fluidHandler == null || fluidHandler.delegate != delegate) { + this.fluidHandler = new FluidHandlerFiltered(delegate); + } + return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(fluidHandler); } - return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(fluidHandler); + return null; } return defaultValue; } @@ -141,7 +143,7 @@ public void readFromNBT(@NotNull NBTTagCompound tagCompound) { private class FluidHandlerFiltered extends FluidHandlerDelegate { - public FluidHandlerFiltered(IFluidHandler delegate) { + public FluidHandlerFiltered(@NotNull IFluidHandler delegate) { super(delegate); } diff --git a/src/main/java/gregtech/common/covers/CoverPump.java b/src/main/java/gregtech/common/covers/CoverPump.java index ebd53eefe50..961e349be6b 100644 --- a/src/main/java/gregtech/common/covers/CoverPump.java +++ b/src/main/java/gregtech/common/covers/CoverPump.java @@ -11,7 +11,12 @@ import gregtech.api.cover.CoverableView; import gregtech.api.gui.GuiTextures; import gregtech.api.gui.ModularUI; -import gregtech.api.gui.widgets.*; +import gregtech.api.gui.widgets.CycleButtonWidget; +import gregtech.api.gui.widgets.ImageWidget; +import gregtech.api.gui.widgets.IncrementButtonWidget; +import gregtech.api.gui.widgets.LabelWidget; +import gregtech.api.gui.widgets.TextFieldWidget2; +import gregtech.api.gui.widgets.WidgetGroup; import gregtech.api.util.GTTransferUtils; import gregtech.client.renderer.texture.Textures; import gregtech.client.renderer.texture.cube.SimpleSidedCubeRenderer; @@ -23,7 +28,12 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.*; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.IStringSerializable; +import net.minecraft.util.ITickable; import net.minecraft.util.math.MathHelper; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.fluids.FluidStack; @@ -397,7 +407,7 @@ public String getName() { private class CoverableFluidHandlerWrapper extends FluidHandlerDelegate { - public CoverableFluidHandlerWrapper(IFluidHandler delegate) { + public CoverableFluidHandlerWrapper(@NotNull IFluidHandler delegate) { super(delegate); } diff --git a/src/main/java/gregtech/common/covers/CoverStorage.java b/src/main/java/gregtech/common/covers/CoverStorage.java index 5d34cb33b17..a63fe3cbd56 100644 --- a/src/main/java/gregtech/common/covers/CoverStorage.java +++ b/src/main/java/gregtech/common/covers/CoverStorage.java @@ -4,8 +4,7 @@ import gregtech.api.cover.CoverDefinition; import gregtech.api.cover.CoverWithUI; import gregtech.api.cover.CoverableView; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; +import gregtech.api.mui.GTGuis; import gregtech.client.renderer.texture.Textures; import net.minecraft.entity.player.EntityPlayer; @@ -23,9 +22,20 @@ import codechicken.lib.render.pipeline.IVertexOperation; import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.value.sync.GuiSyncManager; +import com.cleanroommc.modularui.value.sync.SyncHandlers; +import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.layout.Grid; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + public class CoverStorage extends CoverBase implements CoverWithUI { private final ItemStackHandler storageHandler = new ItemStackHandler(9); @@ -73,17 +83,29 @@ public void onRemoval() { } @Override - public ModularUI createUI(EntityPlayer player) { - ModularUI.Builder builder = new ModularUI.Builder(GuiTextures.BACKGROUND, MAX_WIDTH, MAX_HEIGHT); - builder.label(5, 5, "cover.storage.title"); - for (int index = 0; index < storageHandler.getSlots(); index++) { - builder.slot(storageHandler, index, (index * SLOT_SIZE) + 7, (MAX_HEIGHT - SLOT_SIZE * 5) / 2, true, true, - GuiTextures.SLOT); - } - - builder.bindPlayerInventory(player.inventory, (MAX_HEIGHT - SLOT_SIZE * 2) / 2 - 1); + public boolean usesMui2() { + return true; + } - return builder.build(this, player); + @Override + public ModularPanel buildUI(SidedPosGuiData guiData, GuiSyncManager guiSyncManager) { + guiSyncManager.registerSlotGroup("item_inv", this.storageHandler.getSlots()); + + int rowSize = this.storageHandler.getSlots(); + List> widgets = new ArrayList<>(); + widgets.add(new ArrayList<>()); + for (int i = 0; i < rowSize; i++) { + widgets.get(0) + .add(new ItemSlot().slot(SyncHandlers.itemSlot(this.storageHandler, i).slotGroup("item_inv"))); + } + return GTGuis.createPanel(this, MAX_WIDTH, MAX_HEIGHT) + .child(IKey.lang("cover.storage.title").asWidget().pos(5, 5)) + .bindPlayerInventory() + .child(new Grid() + .top((MAX_HEIGHT - SLOT_SIZE * 5) / 2).left(7).right(7).height(18) + .minElementMargin(0, 0) + .minColWidth(18).minRowHeight(18) + .matrix(widgets)); } /** diff --git a/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java b/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java index 95cc2f172e0..1db9a6087e3 100644 --- a/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java +++ b/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java @@ -2,12 +2,15 @@ import gregtech.api.gui.GuiTextures; import gregtech.api.gui.Widget; +import gregtech.api.gui.resources.TextureArea; import gregtech.api.gui.widgets.DrawableWidget; +import gregtech.api.gui.widgets.ImageCycleButtonWidget; import gregtech.api.gui.widgets.ImageWidget; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.stack.ItemVariantMap; import gregtech.api.unification.stack.MultiItemVariantMap; import gregtech.api.unification.stack.SingleItemVariantMap; +import gregtech.api.util.function.BooleanConsumer; import gregtech.api.util.oreglob.OreGlob; import gregtech.api.util.oreglob.OreGlobCompileResult; import gregtech.common.covers.filter.oreglob.impl.ImpossibleOreGlob; @@ -18,55 +21,90 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import net.minecraft.util.text.TextFormatting; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.Set; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; public class OreDictionaryItemFilter extends ItemFilter { + private final Map> matchCache = new Object2ObjectOpenHashMap<>(); + private final SingleItemVariantMap noOreDictMatch = new SingleItemVariantMap<>(); + protected String expression = ""; + private OreGlob glob = ImpossibleOreGlob.getInstance(); private boolean error; - private final Map> matchCache = new Object2ObjectOpenHashMap<>(); - private final SingleItemVariantMap noOreDictMatch = new SingleItemVariantMap<>(); + private boolean caseSensitive; + /** + * {@code false} requires any of the entry to be match in order for the match to be success, {@code true} requires + * all entries to match + */ + private boolean matchAll; + @NotNull public String getExpression() { return expression; } + @NotNull + public OreGlob getGlob() { + return this.glob; + } + + protected void recompile(@Nullable Consumer<@Nullable OreGlobCompileResult> callback) { + clearCache(); + String expr = this.expression; + if (!expr.isEmpty()) { + OreGlobCompileResult result = OreGlob.compile(expr, !this.caseSensitive); + this.glob = result.getInstance(); + this.error = result.hasError(); + if (callback != null) callback.accept(result); + } else { + this.glob = ImpossibleOreGlob.getInstance(); + this.error = true; + if (callback != null) callback.accept(null); + } + } + + protected void clearCache() { + this.matchCache.clear(); + this.noOreDictMatch.clear(); + } + @Override public void initUI(Consumer widgetGroup) { ItemOreFilterTestSlot[] testSlot = new ItemOreFilterTestSlot[5]; for (int i = 0; i < testSlot.length; i++) { - testSlot[i] = new ItemOreFilterTestSlot(20 + 22 * i, 0); - widgetGroup.accept(testSlot[i]); + ItemOreFilterTestSlot slot = new ItemOreFilterTestSlot(20 + 22 * i, 0); + slot.setGlob(getGlob()); + slot.setMatchAll(this.matchAll); + widgetGroup.accept(slot); + testSlot[i] = slot; } OreGlobCompileStatusWidget compilationStatus = new OreGlobCompileStatusWidget(10, 10); + + Consumer<@Nullable OreGlobCompileResult> compileCallback = result -> { + compilationStatus.setCompileResult(result); + for (ItemOreFilterTestSlot slot : testSlot) { + slot.setGlob(getGlob()); + } + }; + HighlightedTextField textField = new HighlightedTextField(14, 26, 152, 14, () -> this.expression, s -> { if (s.equals(this.expression)) return; this.expression = s; - if (!s.isEmpty()) { - OreGlobCompileResult result = OreGlob.compile(s); - this.glob = result.getInstance(); - this.error = result.hasError(); - compilationStatus.setCompileResult(result); - } else { - this.glob = ImpossibleOreGlob.getInstance(); - this.error = true; - compilationStatus.setCompileResult(null); - } - this.matchCache.clear(); - this.noOreDictMatch.clear(); markDirty(); - for (ItemOreFilterTestSlot slot : testSlot) { - slot.setGlob(this.error ? null : this.glob); - } + recompile(compileCallback); }); compilationStatus.setTextField(textField); @@ -91,7 +129,7 @@ public void initUI(Consumer widgetGroup) { case '*', '?' -> h.format(i, TextFormatting.GREEN); case '!' -> h.format(i, TextFormatting.RED); case '\\' -> h.format(i++, TextFormatting.YELLOW); - case '$' -> { + case '$' -> { // TODO: remove this switch case in 2.9 h.format(i, TextFormatting.DARK_GREEN); for (; i < t.length(); i++) { switch (t.charAt(i)) { @@ -114,6 +152,25 @@ public void initUI(Consumer widgetGroup) { h.format(i + 1, TextFormatting.RESET); } }).setMaxLength(64)); + widgetGroup.accept(new ForcedInitialSyncImageCycleButtonWidget(130, 38, 18, 18, + GuiTextures.ORE_FILTER_BUTTON_CASE_SENSITIVE, () -> this.caseSensitive, caseSensitive -> { + if (this.caseSensitive == caseSensitive) return; + this.caseSensitive = caseSensitive; + markDirty(); + recompile(compileCallback); + }).setTooltipHoverString( + i -> "cover.ore_dictionary_filter.button.case_sensitive." + (i == 0 ? "disabled" : "enabled"))); + widgetGroup.accept(new ForcedInitialSyncImageCycleButtonWidget(148, 38, 18, 18, + GuiTextures.ORE_FILTER_BUTTON_MATCH_ALL, () -> this.matchAll, matchAll -> { + if (this.matchAll == matchAll) return; + this.matchAll = matchAll; + markDirty(); + clearCache(); + for (ItemOreFilterTestSlot slot : testSlot) { + slot.setMatchAll(matchAll); + } + }).setTooltipHoverString( + i -> "cover.ore_dictionary_filter.button.match_all." + (i == 0 ? "disabled" : "enabled"))); } @Override @@ -122,7 +179,7 @@ public Object matchItemStack(ItemStack itemStack) { "wtf is this system?? i can put any non null object here and it i will work??? $arch" : null; } - public boolean matchesItemStack(ItemStack itemStack) { + public boolean matchesItemStack(@NotNull ItemStack itemStack) { if (this.error) return false; Item item = itemStack.getItem(); ItemVariantMap> oreDictEntry = OreDictUnifier.getOreDictionaryEntry(item); @@ -160,7 +217,7 @@ public boolean matchesItemStack(ItemStack itemStack) { } this.matchCache.put(item, cacheEntry); } - boolean matches = this.glob.matches(itemStack); + boolean matches = this.matchAll ? this.glob.matchesAll(itemStack) : this.glob.matchesAny(itemStack); cacheEntry.put(itemStack, matches); return matches; } @@ -181,20 +238,43 @@ public int getTotalOccupiedHeight() { } @Override - public void writeToNBT(NBTTagCompound tagCompound) { - tagCompound.setString("OreDictionaryFilter", expression); + public void writeToNBT(NBTTagCompound tag) { + tag.setString("OreDictionaryFilter", expression); + if (this.caseSensitive) tag.setBoolean("caseSensitive", true); + if (this.matchAll) tag.setBoolean("matchAll", true); } @Override - public void readFromNBT(NBTTagCompound tagCompound) { - this.expression = tagCompound.getString("OreDictionaryFilter"); - if (!this.expression.isEmpty()) { - OreGlobCompileResult result = OreGlob.compile(this.expression); - this.glob = result.getInstance(); - this.error = result.hasError(); - } else { - this.glob = ImpossibleOreGlob.getInstance(); - this.error = true; + public void readFromNBT(NBTTagCompound tag) { + this.expression = tag.getString("OreDictionaryFilter"); + this.caseSensitive = tag.getBoolean("caseSensitive"); + this.matchAll = tag.getBoolean("matchAll"); + recompile(null); + } + + public static class ForcedInitialSyncImageCycleButtonWidget extends ImageCycleButtonWidget { + + private final BooleanConsumer updater; + + public ForcedInitialSyncImageCycleButtonWidget(int xPosition, int yPosition, int width, int height, + TextureArea buttonTexture, BooleanSupplier supplier, + BooleanConsumer updater) { + super(xPosition, yPosition, width, height, buttonTexture, supplier, updater); + this.currentOption = 0; + this.updater = updater; + } + + @Override + public void readUpdateInfo(int id, PacketBuffer buffer) { + if (id == 1) { + int currentOptionCache = this.currentOption; + super.readUpdateInfo(id, buffer); + if (this.currentOption != currentOptionCache) { + this.updater.apply(currentOption >= 1); // call updater to apply necessary state changes + } + } else { + super.readUpdateInfo(id, buffer); + } } } } diff --git a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java index 6b3da3ca6fd..d0c2d44e7df 100644 --- a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java +++ b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java @@ -2,6 +2,8 @@ import gregtech.api.util.LocalizationUtils; +import org.jetbrains.annotations.ApiStatus; + import java.util.Locale; interface OreGlobMessages { @@ -102,21 +104,32 @@ static String compileErrorUnexpectedTokenAfterEOF(String token) { return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unexpected_token_after_eof", token); } + // compilation flags are expected to be removed in future release + + @Deprecated + @SuppressWarnings("DeprecatedIsStillUsed") + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorUnexpectedCompilationFlag() { // Compilation flags in the middle of expression return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unexpected_compilation_flag"); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorEmptyCompilationFlag() { // No compilation flags given return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "empty_compilation_flag"); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorUnknownCompilationFlag(String flag) { // Unknown compilation flag '%s' return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unknown_compilation_flag", flag); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorRedundantCompilationFlag(String flag) { // Compilation flag '%s' written twice return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "redundant_compilation_flag", flag); diff --git a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java index 0bc564d69db..b4cec035040 100644 --- a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java +++ b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java @@ -5,6 +5,7 @@ import gregtech.common.covers.filter.oreglob.node.OreGlobNode; import gregtech.common.covers.filter.oreglob.node.OreGlobNodes; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -14,7 +15,7 @@ /** * Top-down parser for oreGlob expression. - * + * *
  * oreGlob = [ FLAG ], [ or ], EOF
  *
@@ -47,8 +48,7 @@ public final class OreGlobParser {
 
     private boolean error;
 
-    private boolean caseSensitive;
-
+    private boolean ignoreCase;
     private int inputIndex;
 
     private TokenType tokenType;
@@ -56,8 +56,9 @@ public final class OreGlobParser {
     @Nullable
     private String tokenLiteralValue;
 
-    public OreGlobParser(String input) {
+    public OreGlobParser(String input, boolean ignoreCase) {
         this.input = input;
+        this.ignoreCase = ignoreCase;
     }
 
     // Get codepoint at current position and incr index
@@ -68,6 +69,7 @@ private int readNextChar() {
         return input.codePointAt(i);
     }
 
+    @SuppressWarnings("deprecation")
     private void advance() {
         boolean first = this.inputIndex == 0;
         while (true) {
@@ -84,7 +86,7 @@ private void advance() {
                 case '^' -> setCurrentToken(XOR, start, 1);
                 case '*' -> setCurrentToken(ANY, start, 1);
                 case '?' -> setCurrentToken(ANY_CHAR, start, 1);
-                case '$' -> {
+                case '$' -> { // TODO: remove this switch case in 2.9
                     if (!first) {
                         error(OreGlobMessages.compileErrorUnexpectedCompilationFlag(), start, 1);
                     }
@@ -145,8 +147,10 @@ private String gatherLiteralValue() {
         }
     }
 
+    @Deprecated
+    @SuppressWarnings("DeprecatedIsStillUsed")
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     private void gatherFlags(boolean add) {
-        boolean flagsAdded = false;
         while (true) {
             int i = this.inputIndex;
             int c = readNextChar();
@@ -156,39 +160,27 @@ private void gatherFlags(boolean add) {
                     if (c == CHAR_EOF) {
                         error(OreGlobMessages.compileErrorEOFAfterEscape(), i, 1);
                     } else if (add) {
-                        addFlag(c, i);
-                        flagsAdded = true;
+                        addFlag(c);
                         continue;
                     }
                 }
                 case ' ', '\t', '\n', '\r', CHAR_EOF -> {}
                 default -> {
                     if (add) {
-                        addFlag(c, i);
-                        flagsAdded = true;
+                        addFlag(c);
                     }
                     continue;
                 }
             }
-            if (!flagsAdded && add) {
-                error(OreGlobMessages.compileErrorEmptyCompilationFlag(), i, 1);
-            }
+            warn("Compilation flags ('$') are scheduled to be removed in future releases.");
             return;
         }
     }
 
-    private void addFlag(int flag, int index) {
-        switch (flag) {
-            case 'c', 'C' -> {
-                if (this.caseSensitive) {
-                    warn(OreGlobMessages.compileErrorRedundantCompilationFlag("c"), index, 1);
-                } else {
-                    this.caseSensitive = true;
-                }
-            }
-            default -> warn(OreGlobMessages.compileErrorUnknownCompilationFlag(
-                    new StringBuilder().appendCodePoint(flag).toString()), index, 1);
-        }
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
+    private void addFlag(int flag) {
+        if (flag == 'c' || flag == 'C') this.ignoreCase = false;
     }
 
     private boolean advanceIf(TokenType type) {
@@ -296,7 +288,7 @@ private OreGlobNode primary() {
         return switch (tokenType) {
             case LITERAL -> {
                 if (tokenLiteralValue != null) {
-                    OreGlobNode result = OreGlobNodes.match(tokenLiteralValue, !this.caseSensitive);
+                    OreGlobNode result = OreGlobNodes.match(tokenLiteralValue, this.ignoreCase);
                     advance();
                     yield result;
                 } else { // likely caused by program error, not user issue
diff --git a/src/main/java/gregtech/common/gui/package-info.java b/src/main/java/gregtech/common/gui/package-info.java
new file mode 100644
index 00000000000..00cc7c2ad0b
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/package-info.java
@@ -0,0 +1,2 @@
+@Deprecated
+package gregtech.common.gui;
diff --git a/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java b/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
index 6bde8f6cb5b..fd81e80bcd7 100644
--- a/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
@@ -23,7 +23,6 @@
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -32,6 +31,9 @@
  */
 public abstract class OreFilterTestSlot extends WidgetGroup {
 
+    private final ImageWidget match;
+    private final ImageWidget noMatch;
+
     @Nullable
     private OreGlob glob;
     private boolean expectedResult = true;
@@ -48,8 +50,7 @@ public abstract class OreFilterTestSlot extends WidgetGroup {
 
     private boolean initialized = false;
 
-    private final ImageWidget match;
-    private final ImageWidget noMatch;
+    private boolean matchAll;
 
     public OreFilterTestSlot(int xPosition, int yPosition) {
         super(xPosition, yPosition, 18, 18);
@@ -86,30 +87,42 @@ public OreFilterTestSlot onMatchChange(@Nullable BooleanConsumer onMatchChange)
     }
 
     public void setGlob(@Nullable OreGlob glob) {
+        if (this.glob == glob) return;
         this.glob = glob;
         updatePreview();
     }
 
+    public void setMatchAll(boolean matchAll) {
+        if (this.matchAll == matchAll) return;
+        this.matchAll = matchAll;
+        updatePreview();
+    }
+
     protected void updatePreview() {
         if (!this.initialized) return;
         Set oreDicts = getTestCandidates();
         if (oreDicts != null) {
-            boolean success;
             OreGlob glob = this.glob;
             if (oreDicts.isEmpty()) {
                 // no oredict entries
-                this.testResult = Object2BooleanMaps.singleton("", success = glob != null && glob.matches(""));
+                this.testResult = Object2BooleanMaps.singleton("", glob != null && glob.matches(""));
                 this.matchType = MatchType.NO_ORE_DICT_MATCH;
             } else {
                 this.testResult = new Object2BooleanAVLTreeMap<>();
-                success = false;
                 for (String oreDict : oreDicts) {
                     boolean matches = glob != null && glob.matches(oreDict);
                     this.testResult.put(oreDict, matches);
-                    success |= matches;
                 }
                 this.matchType = MatchType.ORE_DICT_MATCH;
             }
+            boolean success = this.matchAll;
+            for (var e : testResult.object2BooleanEntrySet()) {
+                boolean result = e.getBooleanValue();
+                if (result == !this.matchAll) {
+                    success = !this.matchAll;
+                    break;
+                }
+            }
             updateAndNotifyMatchSuccess(this.expectedResult == success);
             this.match.setVisible(this.expectedResult == success);
             this.noMatch.setVisible(this.expectedResult != success);
@@ -131,9 +144,8 @@ private void updateAndNotifyMatchSuccess(boolean newValue) {
     }
 
     /**
-     * Get each test candidate for current state of test slot. An empty collection indicates that the
-     * match is for items without any ore dictionary entry. A {@code null} value indicates that the
-     * input state is invalid or empty.
+     * Get each test candidate for current state of test slot. An empty collection indicates that the match is for items
+     * without any ore dictionary entry. A {@code null} value indicates that the input state is invalid or empty.
      *
      * @return each test candidate for current state of test slot
      */
@@ -168,26 +180,17 @@ public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRender
     @Override
     public void drawInForeground(int mouseX, int mouseY) {
         if (isActive() && isMouseOverElement(mouseX, mouseY)) {
-            List list;
-            switch (this.matchType) {
-                case NO_ORE_DICT_MATCH:
-                    list = Collections.singletonList(I18n.format(this.matchSuccess ?
-                            "cover.ore_dictionary_filter.test_slot.no_oredict.matches" :
-                            "cover.ore_dictionary_filter.test_slot.no_oredict.matches_not"));
-                    break;
-                case ORE_DICT_MATCH:
-                    list = this.testResult.object2BooleanEntrySet().stream().map(
-                            e -> I18n.format(e.getBooleanValue() ?
-                                    "cover.ore_dictionary_filter.test_slot.matches" :
-                                    "cover.ore_dictionary_filter.test_slot.matches_not", e.getKey()))
-                            .collect(Collectors.toList());
-                    break;
-                case INVALID:
-                default:
-                    list = Arrays.asList(LocalizationUtils.formatLines("cover.ore_dictionary_filter.test_slot.info"));
-                    break;
-            }
-            drawHoveringText(ItemStack.EMPTY, list, 300, mouseX, mouseY);
+            drawHoveringText(ItemStack.EMPTY, switch (this.matchType) {
+                case NO_ORE_DICT_MATCH -> Collections.singletonList(I18n.format(this.matchSuccess ?
+                        "cover.ore_dictionary_filter.test_slot.no_oredict.matches" :
+                        "cover.ore_dictionary_filter.test_slot.no_oredict.matches_not"));
+                case ORE_DICT_MATCH -> this.testResult.object2BooleanEntrySet().stream().map(
+                        e -> I18n.format(e.getBooleanValue() ?
+                                "cover.ore_dictionary_filter.test_slot.matches" :
+                                "cover.ore_dictionary_filter.test_slot.matches_not", e.getKey()))
+                        .collect(Collectors.toList());
+                default -> Arrays.asList(LocalizationUtils.formatLines("cover.ore_dictionary_filter.test_slot.info"));
+            }, 300, mouseX, mouseY);
         }
     }
 
diff --git a/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java b/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
index 0588cfc1b83..2be3dcbc766 100644
--- a/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
+++ b/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
@@ -1,15 +1,13 @@
 package gregtech.common.items.behaviors;
 
 import gregtech.api.capability.IGhostSlotConfigurable;
-import gregtech.api.gui.GuiTextures;
-import gregtech.api.gui.ModularUI;
-import gregtech.api.gui.widgets.ClickButtonWidget;
-import gregtech.api.gui.widgets.DynamicLabelWidget;
 import gregtech.api.items.gui.ItemUIFactory;
-import gregtech.api.items.gui.PlayerInventoryHolder;
 import gregtech.api.items.metaitem.stats.IItemBehaviour;
 import gregtech.api.items.metaitem.stats.ISubItemHandler;
 import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.mui.GTGuiTextures;
+import gregtech.api.mui.GTGuis;
+import gregtech.api.mui.factory.MetaItemGuiFactory;
 import gregtech.api.recipes.ingredients.IntCircuitIngredient;
 import gregtech.api.util.GTUtility;
 
@@ -21,6 +19,17 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.api.widget.IWidget;
+import com.cleanroommc.modularui.drawable.ItemDrawable;
+import com.cleanroommc.modularui.factory.HandGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.InteractionSyncHandler;
+import com.cleanroommc.modularui.widgets.ButtonWidget;
+import com.cleanroommc.modularui.widgets.layout.Grid;
+
+import java.util.ArrayList;
 import java.util.List;
 
 public class IntCircuitBehaviour implements IItemBehaviour, ItemUIFactory, ISubItemHandler {
@@ -49,28 +58,50 @@ public ActionResult onItemUse(EntityPlayer player, World world, Block
     public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
         ItemStack heldItem = player.getHeldItem(hand);
         if (!world.isRemote) {
-            PlayerInventoryHolder holder = new PlayerInventoryHolder(player, hand);
-            holder.openUI();
+            MetaItemGuiFactory.open(player, hand);
         }
         return ActionResult.newResult(EnumActionResult.SUCCESS, heldItem);
     }
 
     @Override
-    public ModularUI createUI(PlayerInventoryHolder holder, EntityPlayer entityPlayer) {
-        return ModularUI.builder(GuiTextures.BACKGROUND, 176, 60)
-                .label(9, 8, "metaitem.circuit.integrated.gui")
-                .widget(new DynamicLabelWidget(82, 30,
-                        () -> Integer.toString(IntCircuitIngredient.getCircuitConfiguration(holder.getCurrentItem())),
-                        0x4D4040))
-                .widget(new ClickButtonWidget(15, 24, 20, 20, "-5",
-                        data -> IntCircuitIngredient.adjustConfiguration(holder, -5)))
-                .widget(new ClickButtonWidget(50, 24, 20, 20, "-1",
-                        data -> IntCircuitIngredient.adjustConfiguration(holder, -1)))
-                .widget(new ClickButtonWidget(104, 24, 20, 20, "+1",
-                        data -> IntCircuitIngredient.adjustConfiguration(holder, +1)))
-                .widget(new ClickButtonWidget(141, 24, 20, 20, "+5",
-                        data -> IntCircuitIngredient.adjustConfiguration(holder, +5)))
-                .build(holder, entityPlayer);
+    public ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) {
+        ItemDrawable circuitPreview = new ItemDrawable(guiData.getUsedItemStack());
+        for (int i = 0; i <= 32; i++) {
+            int finalI = i;
+            guiSyncManager.syncValue("config", i, new InteractionSyncHandler()
+                    .setOnMousePressed(b -> {
+                        ItemStack item = IntCircuitIngredient.getIntegratedCircuit(finalI);
+                        item.setCount(guiData.getUsedItemStack().getCount());
+                        circuitPreview.setItem(item);
+                        guiData.getPlayer().setHeldItem(guiData.getHand(), item);
+                    }));
+        }
+
+        List> options = new ArrayList<>();
+        for (int i = 0; i < 4; i++) {
+            options.add(new ArrayList<>());
+            for (int j = 0; j < 9; j++) {
+                int index = i * 9 + j;
+                if (index > 32) break;
+                options.get(i).add(new ButtonWidget<>()
+                        .size(18)
+                        .background(GTGuiTextures.SLOT,
+                                new ItemDrawable(IntCircuitIngredient.getIntegratedCircuit(index)).asIcon().size(16))
+                        .disableHoverBackground()
+                        .syncHandler("config", index));
+            }
+        }
+        return GTGuis.createPanel(guiData.getUsedItemStack(), 176, 120)
+                .child(IKey.lang("metaitem.circuit.integrated.gui").asWidget().pos(5, 5))
+                .child(circuitPreview.asIcon().size(16).asWidget()
+                        .size(18)
+                        .top(19).alignX(0.5f)
+                        .background(GTGuiTextures.SLOT))
+                .child(new Grid()
+                        .left(7).right(7).top(41).height(4 * 18)
+                        .matrix(options)
+                        .minColWidth(18).minRowHeight(18)
+                        .minElementMargin(0, 0));
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java b/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
index 1e702d37b48..4a9c1e17492 100644
--- a/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
+++ b/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
@@ -20,17 +20,79 @@
 import gregtech.common.blocks.BlockTurbineCasing;
 import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.metatileentities.converter.MetaTileEntityConverter;
-import gregtech.common.metatileentities.electric.*;
+import gregtech.common.metatileentities.electric.MetaTileEntityAlarm;
+import gregtech.common.metatileentities.electric.MetaTileEntityBatteryBuffer;
+import gregtech.common.metatileentities.electric.MetaTileEntityBlockBreaker;
+import gregtech.common.metatileentities.electric.MetaTileEntityCharger;
+import gregtech.common.metatileentities.electric.MetaTileEntityDiode;
+import gregtech.common.metatileentities.electric.MetaTileEntityFisher;
+import gregtech.common.metatileentities.electric.MetaTileEntityGasCollector;
+import gregtech.common.metatileentities.electric.MetaTileEntityHull;
+import gregtech.common.metatileentities.electric.MetaTileEntityItemCollector;
+import gregtech.common.metatileentities.electric.MetaTileEntityMagicEnergyAbsorber;
+import gregtech.common.metatileentities.electric.MetaTileEntityMiner;
+import gregtech.common.metatileentities.electric.MetaTileEntityPump;
+import gregtech.common.metatileentities.electric.MetaTileEntityRockBreaker;
+import gregtech.common.metatileentities.electric.MetaTileEntitySingleCombustion;
+import gregtech.common.metatileentities.electric.MetaTileEntitySingleTurbine;
+import gregtech.common.metatileentities.electric.MetaTileEntityTransformer;
+import gregtech.common.metatileentities.electric.MetaTileEntityWorldAccelerator;
+import gregtech.common.metatileentities.electric.SimpleMachineMetaTileEntityResizable;
 import gregtech.common.metatileentities.miner.LargeMinerTypes;
 import gregtech.common.metatileentities.miner.MetaTileEntityLargeMiner;
 import gregtech.common.metatileentities.miner.MetaTileEntityMiner;
-import gregtech.common.metatileentities.multi.*;
-import gregtech.common.metatileentities.multi.electric.*;
+import gregtech.common.metatileentities.multi.BoilerType;
+import gregtech.common.metatileentities.multi.MetaTileEntityCokeOven;
+import gregtech.common.metatileentities.multi.MetaTileEntityCokeOvenHatch;
+import gregtech.common.metatileentities.multi.MetaTileEntityLargeBoiler;
+import gregtech.common.metatileentities.multi.MetaTileEntityMultiblockTank;
+import gregtech.common.metatileentities.multi.MetaTileEntityPrimitiveBlastFurnace;
+import gregtech.common.metatileentities.multi.MetaTileEntityPrimitiveWaterPump;
+import gregtech.common.metatileentities.multi.MetaTileEntityPumpHatch;
+import gregtech.common.metatileentities.multi.MetaTileEntityTankValve;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityActiveTransformer;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityAssemblyLine;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityCleanroom;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityCrackingUnit;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityDataBank;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityDistillationTower;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityElectricBlastFurnace;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityFluidDrill;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityFusionReactor;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityHPCA;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityImplosionCompressor;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityLargeChemicalReactor;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityLargeMiner;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityMultiSmelter;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityNetworkSwitch;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityPowerSubstation;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityProcessingArray;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityPyrolyseOven;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityResearchStation;
+import gregtech.common.metatileentities.multi.electric.MetaTileEntityVacuumFreezer;
 import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityCentralMonitor;
 import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityMonitorScreen;
 import gregtech.common.metatileentities.multi.electric.generator.MetaTileEntityLargeCombustionEngine;
 import gregtech.common.metatileentities.multi.electric.generator.MetaTileEntityLargeTurbine;
-import gregtech.common.metatileentities.multi.multiblockpart.*;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityAutoMaintenanceHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityCleaningMaintenanceHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityComputationHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityDataAccessHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityEnergyHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityFluidHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityItemBus;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityLaserHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMachineHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMaintenanceHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMufflerHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMultiFluidHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityObjectHolder;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityOpticalDataHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityPassthroughHatchFluid;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityPassthroughHatchItem;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityReservoirHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityRotorHolder;
+import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntitySubstationEnergyHatch;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEInputBus;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEInputHatch;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEOutputBus;
@@ -42,13 +104,28 @@
 import gregtech.common.metatileentities.multi.steam.MetaTileEntitySteamGrinder;
 import gregtech.common.metatileentities.multi.steam.MetaTileEntitySteamOven;
 import gregtech.common.metatileentities.primitive.MetaTileEntityCharcoalPileIgniter;
-import gregtech.common.metatileentities.steam.*;
+import gregtech.common.metatileentities.steam.SteamAlloySmelter;
+import gregtech.common.metatileentities.steam.SteamCompressor;
+import gregtech.common.metatileentities.steam.SteamExtractor;
+import gregtech.common.metatileentities.steam.SteamFurnace;
+import gregtech.common.metatileentities.steam.SteamHammer;
+import gregtech.common.metatileentities.steam.SteamMacerator;
+import gregtech.common.metatileentities.steam.SteamMiner;
+import gregtech.common.metatileentities.steam.SteamRockBreaker;
 import gregtech.common.metatileentities.steam.boiler.SteamCoalBoiler;
 import gregtech.common.metatileentities.steam.boiler.SteamLavaBoiler;
 import gregtech.common.metatileentities.steam.boiler.SteamSolarBoiler;
 import gregtech.common.metatileentities.steam.multiblockpart.MetaTileEntitySteamHatch;
 import gregtech.common.metatileentities.steam.multiblockpart.MetaTileEntitySteamItemBus;
-import gregtech.common.metatileentities.storage.*;
+import gregtech.common.metatileentities.storage.MetaTileEntityBuffer;
+import gregtech.common.metatileentities.storage.MetaTileEntityCrate;
+import gregtech.common.metatileentities.storage.MetaTileEntityCreativeChest;
+import gregtech.common.metatileentities.storage.MetaTileEntityCreativeEnergy;
+import gregtech.common.metatileentities.storage.MetaTileEntityCreativeTank;
+import gregtech.common.metatileentities.storage.MetaTileEntityDrum;
+import gregtech.common.metatileentities.storage.MetaTileEntityQuantumChest;
+import gregtech.common.metatileentities.storage.MetaTileEntityQuantumTank;
+import gregtech.common.metatileentities.storage.MetaTileEntityWorkbench;
 import gregtech.common.pipelike.fluidpipe.longdistance.MetaTileEntityLDFluidEndpoint;
 import gregtech.common.pipelike.itempipe.longdistance.MetaTileEntityLDItemEndpoint;
 import gregtech.integration.jei.multiblock.MultiblockInfoCategory;
@@ -193,7 +270,7 @@ public class MetaTileEntities {
                                                                                                                                            // UHV
     public static final MetaTileEntityRotorHolder[] ROTOR_HOLDER = new MetaTileEntityRotorHolder[6]; // HV, EV, IV, LuV,
                                                                                                      // ZPM, UV
-    public static final MetaTileEntityMufflerHatch[] MUFFLER_HATCH = new MetaTileEntityMufflerHatch[GTValues.UV]; // LV-UV
+    public static final MetaTileEntityMufflerHatch[] MUFFLER_HATCH = new MetaTileEntityMufflerHatch[GTValues.UV + 1]; // LV-UV
     public static final MetaTileEntityFusionReactor[] FUSION_REACTOR = new MetaTileEntityFusionReactor[3];
     public static final MetaTileEntityQuantumChest[] QUANTUM_CHEST = new MetaTileEntityQuantumChest[10];
     public static final MetaTileEntityQuantumTank[] QUANTUM_TANK = new MetaTileEntityQuantumTank[10];
@@ -304,7 +381,6 @@ public class MetaTileEntities {
     public static MetaTileEntityActiveTransformer ACTIVE_TRANSFORMER;
 
     // STORAGE SECTION
-    public static MetaTileEntityLockedSafe LOCKED_SAFE;
     public static MetaTileEntityTankValve WOODEN_TANK_VALVE;
     public static MetaTileEntityTankValve STEEL_TANK_VALVE;
     public static MetaTileEntityMultiblockTank WOODEN_TANK;
@@ -1011,19 +1087,19 @@ public static void init() {
 
         // Crates, IDs 1625-1639
         WOODEN_CRATE = registerMetaTileEntity(1625,
-                new MetaTileEntityCrate(gregtechId("crate.wood"), Materials.Wood, 27));
+                new MetaTileEntityCrate(gregtechId("crate.wood"), Materials.Wood, 27, 9));
         BRONZE_CRATE = registerMetaTileEntity(1626,
-                new MetaTileEntityCrate(gregtechId("crate.bronze"), Materials.Bronze, 54));
+                new MetaTileEntityCrate(gregtechId("crate.bronze"), Materials.Bronze, 54, 9));
         STEEL_CRATE = registerMetaTileEntity(1627,
-                new MetaTileEntityCrate(gregtechId("crate.steel"), Materials.Steel, 72));
+                new MetaTileEntityCrate(gregtechId("crate.steel"), Materials.Steel, 72, 9));
         ALUMINIUM_CRATE = registerMetaTileEntity(1628,
-                new MetaTileEntityCrate(gregtechId("crate.aluminium"), Materials.Aluminium, 90));
+                new MetaTileEntityCrate(gregtechId("crate.aluminium"), Materials.Aluminium, 90, 10));
         STAINLESS_STEEL_CRATE = registerMetaTileEntity(1629,
-                new MetaTileEntityCrate(gregtechId("crate.stainless_steel"), Materials.StainlessSteel, 108));
+                new MetaTileEntityCrate(gregtechId("crate.stainless_steel"), Materials.StainlessSteel, 108, 12));
         TITANIUM_CRATE = registerMetaTileEntity(1630,
-                new MetaTileEntityCrate(gregtechId("crate.titanium"), Materials.Titanium, 126));
+                new MetaTileEntityCrate(gregtechId("crate.titanium"), Materials.Titanium, 126, 14));
         TUNGSTENSTEEL_CRATE = registerMetaTileEntity(1631,
-                new MetaTileEntityCrate(gregtechId("crate.tungstensteel"), Materials.TungstenSteel, 144));
+                new MetaTileEntityCrate(gregtechId("crate.tungstensteel"), Materials.TungstenSteel, 144, 16));
 
         // Rotor Holder, IDs 1640-1645
         ROTOR_HOLDER[0] = registerMetaTileEntity(1640,
@@ -1040,7 +1116,6 @@ public static void init() {
                 new MetaTileEntityRotorHolder(gregtechId("rotor_holder.uv"), GTValues.UV));
 
         // Misc, IDs 1646-1999
-        LOCKED_SAFE = registerMetaTileEntity(1646, new MetaTileEntityLockedSafe(gregtechId("locked_safe")));
         WORKBENCH = registerMetaTileEntity(1647, new MetaTileEntityWorkbench(gregtechId("workbench")));
         PRIMITIVE_WATER_PUMP = registerMetaTileEntity(1648,
                 new MetaTileEntityPrimitiveWaterPump(gregtechId("primitive_water_pump")));
@@ -1064,12 +1139,13 @@ public static void init() {
         CLEANING_MAINTENANCE_HATCH = registerMetaTileEntity(1401,
                 new MetaTileEntityCleaningMaintenanceHatch(gregtechId("maintenance_hatch_cleanroom_auto")));
 
-        // Muffler Hatches, IDs 1657-
-        for (int i = 0; i < MUFFLER_HATCH.length; i++) {
-            String voltageName = GTValues.VN[i + 1].toLowerCase();
-            MUFFLER_HATCH[i] = new MetaTileEntityMufflerHatch(gregtechId("muffler_hatch." + voltageName), i + 1);
+        // Muffler Hatches, IDs 1657-1664
+        for (int i = 0; i < MUFFLER_HATCH.length - 1; i++) {
+            int tier = i + 1;
+            String voltageName = GTValues.VN[tier].toLowerCase();
+            MUFFLER_HATCH[tier] = new MetaTileEntityMufflerHatch(gregtechId("muffler_hatch." + voltageName), tier);
 
-            registerMetaTileEntity(1657 + i, MUFFLER_HATCH[i]);
+            registerMetaTileEntity(1657 + i, MUFFLER_HATCH[tier]);
         }
 
         CLIPBOARD_TILE = registerMetaTileEntity(1666, new MetaTileEntityClipboard(gregtechId("clipboard")));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 8e667bedfcb..90b8b70886c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -30,6 +30,7 @@
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.recipeproperties.FusionEUToStartProperty;
+import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage;
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
@@ -630,6 +631,19 @@ public boolean checkRecipe(@NotNull Recipe recipe) {
             return true;
         }
 
+        @Override
+        protected void modifyOverclockPre(int @NotNull [] values, @NotNull IRecipePropertyStorage storage) {
+            super.modifyOverclockPre(values, storage);
+
+            // Limit the number of OCs to the difference in fusion reactor MK.
+            // I.e., a MK2 reactor can overclock a MK1 recipe once, and a
+            // MK3 reactor can overclock a MK2 recipe once, or a MK1 recipe twice.
+            long euToStart = storage.getRecipePropertyValue(FusionEUToStartProperty.getInstance(), 0L);
+            int fusionTier = FusionEUToStartProperty.getFusionTier(euToStart);
+            if (fusionTier != 0) fusionTier -= MetaTileEntityFusionReactor.this.tier;
+            values[2] = Math.min(fusionTier, values[2]);
+        }
+
         @NotNull
         @Override
         public NBTTagCompound serializeNBT() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 777307110af..16522799167 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -106,7 +106,7 @@ protected void formStructure(PatternMatchContext context) {
     protected long getMaxVoltage() {
         long maxProduction = recipeMapWorkable.getMaxVoltage();
         long currentProduction = ((LargeTurbineWorkableHandler) recipeMapWorkable).boostProduction((int) maxProduction);
-        if (isActive() && currentProduction < maxProduction) {
+        if (isActive() && currentProduction <= maxProduction) {
             return recipeMapWorkable.getMaxVoltage();
         } else {
             return 0L;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
index e44a0953d4e..949f47b77ca 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
@@ -4,18 +4,15 @@
 import gregtech.api.capability.impl.GhostCircuitItemStackHandler;
 import gregtech.api.capability.impl.ItemHandlerList;
 import gregtech.api.capability.impl.NotifiableItemStackHandler;
-import gregtech.api.gui.GuiTextures;
-import gregtech.api.gui.ModularUI;
-import gregtech.api.gui.ModularUI.Builder;
-import gregtech.api.gui.resources.TextureArea;
-import gregtech.api.gui.widgets.GhostCircuitSlotWidget;
-import gregtech.api.gui.widgets.SlotWidget;
 import gregtech.api.items.itemhandlers.GTItemStackHandler;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.mui.GTGuiTextures;
+import gregtech.api.mui.GTGuis;
+import gregtech.api.mui.widget.GhostCircuitSlotWidget;
 import gregtech.api.util.GTHashMaps;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.renderer.texture.cube.SimpleOverlayRenderer;
@@ -38,6 +35,20 @@
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.api.widget.IWidget;
+import com.cleanroommc.modularui.factory.PosGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.BoolValue;
+import com.cleanroommc.modularui.value.sync.BooleanSyncValue;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.SyncHandlers;
+import com.cleanroommc.modularui.widget.Widget;
+import com.cleanroommc.modularui.widgets.ItemSlot;
+import com.cleanroommc.modularui.widgets.SlotGroupWidget;
+import com.cleanroommc.modularui.widgets.ToggleButton;
+import com.cleanroommc.modularui.widgets.layout.Column;
+import com.cleanroommc.modularui.widgets.layout.Grid;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -126,8 +137,8 @@ public void update() {
                 // Exclude the ghost circuit inventory from the auto collapse, so it does not extract any ghost circuits
                 // from the slot
                 IItemHandlerModifiable inventory = (isExportHatch ? this.getExportItems() : super.getImportItems());
-                if (isExportHatch ? this.getNotifiedItemOutputList().contains(inventory) :
-                        this.getNotifiedItemInputList().contains(inventory)) {
+                if (!isAttachedToMultiBlock() || (isExportHatch ? this.getNotifiedItemOutputList().contains(inventory) :
+                        this.getNotifiedItemInputList().contains(inventory))) {
                     collapseInventorySlotContents(inventory);
                 }
             }
@@ -249,44 +260,77 @@ public void registerAbilities(List abilityList) {
     }
 
     @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
-        int rowSize = (int) Math.sqrt(getInventorySize());
-        return createUITemplate(entityPlayer, rowSize)
-                .build(getHolder(), entityPlayer);
+    public boolean usesMui2() {
+        return true;
     }
 
-    private ModularUI.Builder createUITemplate(EntityPlayer player, int gridSize) {
-        int backgroundWidth = gridSize > 6 ? 176 + (gridSize - 6) * 18 : 176;
-        int center = backgroundWidth / 2;
-
-        int gridStartX = center - (gridSize * 9);
-
-        int inventoryStartX = center - 9 - 4 * 18;
-        int inventoryStartY = 18 + 18 * gridSize + 12;
-
-        Builder builder = ModularUI.builder(GuiTextures.BACKGROUND, backgroundWidth, 18 + 18 * gridSize + 94)
-                .label(10, 5, getMetaFullName());
-
-        for (int y = 0; y < gridSize; y++) {
-            for (int x = 0; x < gridSize; x++) {
-                int index = y * gridSize + x;
-
-                builder.widget(new SlotWidget(isExportHatch ? exportItems : importItems, index,
-                        gridStartX + x * 18, 18 + y * 18, true, !isExportHatch)
-                                .setBackgroundTexture(GuiTextures.SLOT));
+    @Override
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
+        int rowSize = (int) Math.sqrt(getInventorySize());
+        guiSyncManager.registerSlotGroup("item_inv", rowSize);
+
+        int backgroundWidth = Math.max(
+                9 * 18 + 18 + 14 + 5,   // Player Inv width
+                rowSize * 18 + 14); // Bus Inv width
+        int backgroundHeight = 18 + 18 * rowSize + 94;
+
+        List> widgets = new ArrayList<>();
+        for (int i = 0; i < rowSize; i++) {
+            widgets.add(new ArrayList<>());
+            for (int j = 0; j < rowSize; j++) {
+                widgets.get(i)
+                        .add(new ItemSlot()
+                                .slot(SyncHandlers.itemSlot(isExportHatch ? exportItems : importItems, i * rowSize + j)
+                                        .slotGroup("item_inv")
+                                        .accessibility(!isExportHatch, true)));
             }
         }
 
-        if (hasGhostCircuitInventory() && this.circuitInventory != null) {
-            int circuitX = gridSize > 6 ? gridStartX + gridSize * 18 + 9 : inventoryStartX + 8 * 18;
-            int circuitY = gridSize * 18;
-
-            SlotWidget circuitSlot = new GhostCircuitSlotWidget(circuitInventory, 0, circuitX, circuitY)
-                    .setBackgroundTexture(GuiTextures.SLOT, getCircuitSlotOverlay());
-            builder.widget(circuitSlot.setConsumer(this::getCircuitSlotTooltip));
-        }
-
-        return builder.bindPlayerInventory(player.inventory, GuiTextures.SLOT, inventoryStartX, inventoryStartY);
+        BooleanSyncValue workingStateValue = new BooleanSyncValue(() -> workingEnabled, val -> workingEnabled = val);
+        guiSyncManager.syncValue("working_state", workingStateValue);
+        BooleanSyncValue collapseStateValue = new BooleanSyncValue(() -> autoCollapse, val -> autoCollapse = val);
+        guiSyncManager.syncValue("collapse_state", collapseStateValue);
+
+        boolean hasGhostCircuit = hasGhostCircuitInventory() && this.circuitInventory != null;
+
+        return GTGuis.createPanel(this, backgroundWidth, backgroundHeight)
+                .child(IKey.lang(getMetaFullName()).asWidget().pos(5, 5))
+                .child(SlotGroupWidget.playerInventory().left(7).bottom(7))
+                .child(new Grid()
+                        .top(18).height(rowSize * 18)
+                        .minElementMargin(0, 0)
+                        .minColWidth(18).minRowHeight(18)
+                        .alignX(0.5f)
+                        .matrix(widgets))
+                .child(new Column()
+                        .pos(backgroundWidth - 7 - 18, backgroundHeight - 18 * 4 - 7 - 5)
+                        .width(18).height(18 * 4 + 5)
+                        .child(GTGuiTextures.getLogo(getUITheme()).asWidget().size(17).top(18 * 3 + 5))
+                        .child(new ToggleButton()
+                                .top(18 * 2)
+                                .value(new BoolValue.Dynamic(workingStateValue::getBoolValue,
+                                        workingStateValue::setBoolValue))
+                                .overlay(GTGuiTextures.BUTTON_ITEM_OUTPUT)
+                                .tooltipBuilder(t -> t.setAutoUpdate(true)
+                                        .addLine(workingStateValue.getBoolValue() ?
+                                                IKey.lang("gregtech.gui.item_auto_output.tooltip.enabled") :
+                                                IKey.lang("gregtech.gui.item_auto_output.tooltip.disabled"))))
+                        .child(new ToggleButton()
+                                .top(18)
+                                .value(new BoolValue.Dynamic(collapseStateValue::getBoolValue,
+                                        collapseStateValue::setBoolValue))
+                                .overlay(GTGuiTextures.BUTTON_AUTO_COLLAPSE)
+                                .tooltipBuilder(t -> t.setAutoUpdate(true)
+                                        .addLine(collapseStateValue.getBoolValue() ?
+                                                IKey.lang("gregtech.gui.item_auto_collapse.tooltip.enabled") :
+                                                IKey.lang("gregtech.gui.item_auto_collapse.tooltip.disabled"))))
+                        .childIf(hasGhostCircuit, new GhostCircuitSlotWidget()
+                                .slot(SyncHandlers.itemSlot(circuitInventory, 0))
+                                .background(GTGuiTextures.SLOT, GTGuiTextures.INT_CIRCUIT_OVERLAY))
+                        .childIf(!hasGhostCircuit, new Widget<>()
+                                .background(GTGuiTextures.SLOT, GTGuiTextures.BUTTON_X)
+                                .tooltip(t -> t.addLine(
+                                        IKey.lang("gregtech.gui.configurator_slot.unavailable.tooltip")))));
     }
 
     @Override
@@ -294,23 +338,6 @@ public boolean hasGhostCircuitInventory() {
         return !this.isExportHatch;
     }
 
-    // Method provided to override
-    protected TextureArea getCircuitSlotOverlay() {
-        return GuiTextures.INT_CIRCUIT_OVERLAY;
-    }
-
-    // Method provided to override
-    protected void getCircuitSlotTooltip(@NotNull SlotWidget widget) {
-        String configString;
-        if (circuitInventory == null || circuitInventory.getCircuitValue() == GhostCircuitItemStackHandler.NO_CONFIG) {
-            configString = new TextComponentTranslation("gregtech.gui.configurator_slot.no_value").getFormattedText();
-        } else {
-            configString = String.valueOf(circuitInventory.getCircuitValue());
-        }
-
-        widget.setTooltipText("gregtech.gui.configurator_slot.tooltip", configString);
-    }
-
     private static void collapseInventorySlotContents(IItemHandlerModifiable inventory) {
         // Gather a snapshot of the provided inventory
         Object2IntMap inventoryContents = GTHashMaps.fromItemHandler(inventory, true);
@@ -352,21 +379,13 @@ private static void collapseInventorySlotContents(IItemHandlerModifiable invento
     @Override
     public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                       CuboidRayTraceResult hitResult) {
-        boolean isAttached = false;
-        if (this.isAttachedToMultiBlock()) {
-            setAutoCollapse(!this.autoCollapse);
-            isAttached = true;
-        }
+        setAutoCollapse(!this.autoCollapse);
 
         if (!getWorld().isRemote) {
-            if (isAttached) {
-                if (this.autoCollapse) {
-                    playerIn.sendStatusMessage(new TextComponentTranslation("gregtech.bus.collapse_true"), true);
-                } else {
-                    playerIn.sendStatusMessage(new TextComponentTranslation("gregtech.bus.collapse_false"), true);
-                }
+            if (this.autoCollapse) {
+                playerIn.sendStatusMessage(new TextComponentTranslation("gregtech.bus.collapse_true"), true);
             } else {
-                playerIn.sendStatusMessage(new TextComponentTranslation("gregtech.bus.collapse.error"), true);
+                playerIn.sendStatusMessage(new TextComponentTranslation("gregtech.bus.collapse_false"), true);
             }
         }
         return true;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
index 4b3e75a4c2c..5bc07d2e793 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
@@ -11,6 +11,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.util.GTTransferUtils;
 import gregtech.api.util.GTUtility;
@@ -69,7 +70,7 @@ public void update() {
 
         if (getWorld().isRemote && getController() instanceof MultiblockWithDisplayBase controller &&
                 controller.isActive()) {
-            VanillaParticleEffects.MUFFLER_SMOKE.runEffect(this);
+            VanillaParticleEffects.mufflerEffect(this, controller.getMufflerParticle());
         }
     }
 
@@ -112,12 +113,15 @@ private boolean checkFrontFaceFree() {
         return blockState.getBlock().isAir(blockState, getWorld(), frontPos) || GTUtility.isBlockSnow(blockState);
     }
 
-    /** @deprecated Use {@link VanillaParticleEffects#MUFFLER_SMOKE} instead. */
+    /** @deprecated No longer needed. Multiblock controller sets the particle type. */
     @Deprecated
     @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     @SideOnly(Side.CLIENT)
     public void pollutionParticles() {
-        VanillaParticleEffects.MUFFLER_SMOKE.runEffect(this);
+        MultiblockControllerBase controller = getController();
+        if (controller instanceof MultiblockWithDisplayBase displayBase) {
+            VanillaParticleEffects.mufflerEffect(this, displayBase.getMufflerParticle());
+        }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java b/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
index 37159f0c00d..97f855406e2 100644
--- a/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
@@ -1,11 +1,11 @@
 package gregtech.common.metatileentities.steam.multiblockpart;
 
-import gregtech.api.gui.GuiTextures;
-import gregtech.api.gui.ModularUI;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.mui.GTGuiTheme;
+import gregtech.api.mui.GTGuis;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.renderer.texture.cube.SimpleOverlayRenderer;
@@ -14,7 +14,6 @@
 import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityItemBus;
 
 import net.minecraft.client.resources.I18n;
-import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.world.World;
@@ -23,9 +22,18 @@
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.api.widget.IWidget;
+import com.cleanroommc.modularui.factory.PosGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.SyncHandlers;
+import com.cleanroommc.modularui.widgets.ItemSlot;
+import com.cleanroommc.modularui.widgets.layout.Grid;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
 import java.util.List;
 
 public class MetaTileEntitySteamItemBus extends MetaTileEntityItemBus {
@@ -77,28 +85,41 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
         }
     }
 
+    @Override
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
+        guiSyncManager.registerSlotGroup("item_inv", 2);
+
+        List> widgets = new ArrayList<>();
+        for (int i = 0; i < 2; i++) {
+            widgets.add(new ArrayList<>());
+            for (int j = 0; j < 2; j++) {
+                widgets.get(i)
+                        .add(new ItemSlot()
+                                .slot(SyncHandlers.itemSlot(isExportHatch ? exportItems : importItems, i * 2 + j)
+                                        .slotGroup("item_inv")
+                                        .accessibility(!isExportHatch, true)));
+            }
+        }
+
+        return GTGuis.createPanel(this, 176, 166)
+                .child(IKey.lang(getMetaFullName()).asWidget().pos(5, 5))
+                .bindPlayerInventory()
+                .child(new Grid()
+                        .top(29).height(36)
+                        .minElementMargin(0, 0)
+                        .minColWidth(18).minRowHeight(18)
+                        .alignX(0.5f)
+                        .matrix(widgets));
+    }
+
     @Override
     public int getDefaultPaintingColor() {
         return 0xFFFFFF;
     }
 
-    // Override UI to make it a Steam UI
     @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
-        ModularUI.Builder builder = ModularUI.builder(GuiTextures.BACKGROUND_STEAM.get(IS_STEEL), 176, 166)
-                .label(6, 6, getMetaFullName());
-
-        for (int y = 0; y < 2; y++) {
-            for (int x = 0; x < 2; x++) {
-                int index = y * 2 + x;
-                builder.slot(isExportHatch ? exportItems : importItems, index, 70 + x * 18, 31 + y * 18, true,
-                        !isExportHatch,
-                        GuiTextures.SLOT_STEAM.get(IS_STEEL));
-            }
-        }
-        builder.shouldColor(false);
-        builder.bindPlayerInventory(entityPlayer.inventory, GuiTextures.SLOT_STEAM.get(IS_STEEL), 7, 83);
-        return builder.build(getHolder(), entityPlayer);
+    public GTGuiTheme getUITheme() {
+        return IS_STEEL ? GTGuiTheme.STEEL : GTGuiTheme.BRONZE;
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
index 1a7d5c00752..44859246953 100644
--- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
+++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
@@ -1,12 +1,10 @@
 package gregtech.common.metatileentities.storage;
 
-import gregtech.api.gui.GuiTextures;
-import gregtech.api.gui.ModularUI;
-import gregtech.api.gui.ModularUI.Builder;
 import gregtech.api.items.itemhandlers.GTItemStackHandler;
 import gregtech.api.items.toolitem.ToolClasses;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.mui.GTGuis;
 import gregtech.api.recipes.ModHandler;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.GTUtility;
@@ -33,9 +31,18 @@
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.api.widget.IWidget;
+import com.cleanroommc.modularui.factory.PosGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.SyncHandlers;
+import com.cleanroommc.modularui.widgets.ItemSlot;
+import com.cleanroommc.modularui.widgets.layout.Grid;
 import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import static gregtech.api.capability.GregtechDataCodes.IS_TAPED;
@@ -45,20 +52,22 @@ public class MetaTileEntityCrate extends MetaTileEntity {
 
     private final Material material;
     private final int inventorySize;
+    private final int rowSize;
     protected ItemStackHandler inventory;
     private boolean isTaped;
     private final String TAPED_NBT = "Taped";
 
-    public MetaTileEntityCrate(ResourceLocation metaTileEntityId, Material material, int inventorySize) {
+    public MetaTileEntityCrate(ResourceLocation metaTileEntityId, Material material, int inventorySize, int rowSize) {
         super(metaTileEntityId);
         this.material = material;
         this.inventorySize = inventorySize;
+        this.rowSize = rowSize;
         initializeInventory();
     }
 
     @Override
     public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
-        return new MetaTileEntityCrate(metaTileEntityId, material, inventorySize);
+        return new MetaTileEntityCrate(metaTileEntityId, material, inventorySize, rowSize);
     }
 
     @Override
@@ -132,18 +141,31 @@ public int getDefaultPaintingColor() {
     }
 
     @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
-        int factor = inventorySize / 9 > 8 ? 18 : 9;
-        Builder builder = ModularUI
-                .builder(GuiTextures.BACKGROUND, 176 + (factor == 18 ? 176 : 0), 8 + inventorySize / factor * 18 + 104)
-                .label(5, 5, getMetaFullName());
-        for (int i = 0; i < inventorySize; i++) {
-            builder.slot(inventory, i, 7 * (factor == 18 ? 2 : 1) + i % factor * 18, 18 + i / factor * 18,
-                    GuiTextures.SLOT);
+    public boolean usesMui2() {
+        return true;
+    }
+
+    @Override
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
+        guiSyncManager.registerSlotGroup("item_inv", rowSize);
+
+        int rows = inventorySize / rowSize;
+        List> widgets = new ArrayList<>();
+        for (int i = 0; i < rows; i++) {
+            widgets.add(new ArrayList<>());
+            for (int j = 0; j < this.rowSize; j++) {
+                widgets.get(i).add(new ItemSlot().slot(SyncHandlers.itemSlot(inventory, i * rowSize + j)
+                        .slotGroup("item_inv")));
+            }
         }
-        builder.bindPlayerInventory(entityPlayer.inventory, GuiTextures.SLOT, 7 + (factor == 18 ? 88 : 0),
-                18 + inventorySize / factor * 18 + 11);
-        return builder.build(getHolder(), entityPlayer);
+        return GTGuis.createPanel(this, rowSize * 18 + 14, 18 + 4 * 18 + 5 + 14 + 18 * rows)
+                .child(IKey.lang(getMetaFullName()).asWidget().pos(5, 5))
+                .bindPlayerInventory()
+                .child(new Grid()
+                        .top(18).left(7).right(7).height(rows * 18)
+                        .minElementMargin(0, 0)
+                        .minColWidth(18).minRowHeight(18)
+                        .matrix(widgets));
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityLockedSafe.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityLockedSafe.java
deleted file mode 100644
index d8230181390..00000000000
--- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityLockedSafe.java
+++ /dev/null
@@ -1,432 +0,0 @@
-package gregtech.common.metatileentities.storage;
-
-import gregtech.api.GTValues;
-import gregtech.api.gui.GuiTextures;
-import gregtech.api.gui.ModularUI;
-import gregtech.api.gui.widgets.LabelWidget;
-import gregtech.api.gui.widgets.ProgressWidget;
-import gregtech.api.gui.widgets.ProgressWidget.MoveType;
-import gregtech.api.gui.widgets.ServerWidgetGroup;
-import gregtech.api.gui.widgets.SlotWidget;
-import gregtech.api.items.itemhandlers.GTItemStackHandler;
-import gregtech.api.metatileentity.IFastRenderMetaTileEntity;
-import gregtech.api.metatileentity.MetaTileEntity;
-import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.recipes.ingredients.GTRecipeInput;
-import gregtech.api.recipes.ingredients.GTRecipeItemInput;
-import gregtech.api.recipes.ingredients.GTRecipeOreInput;
-import gregtech.api.util.GTUtility;
-import gregtech.client.renderer.texture.Textures;
-import gregtech.common.worldgen.LootTableHelper;
-import gregtech.loaders.recipe.CraftingComponent;
-import gregtech.loaders.recipe.CraftingComponent.Component;
-
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.entity.player.EntityPlayer;
-import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.network.PacketBuffer;
-import net.minecraft.util.EnumFacing;
-import net.minecraft.util.EnumHand;
-import net.minecraft.util.NonNullList;
-import net.minecraft.util.ResourceLocation;
-import net.minecraft.util.math.AxisAlignedBB;
-import net.minecraft.world.WorldServer;
-import net.minecraft.world.storage.loot.LootContext;
-import net.minecraft.world.storage.loot.LootTable;
-import net.minecraftforge.common.util.Constants.NBT;
-import net.minecraftforge.items.ItemStackHandler;
-import net.minecraftforge.oredict.OreDictionary;
-
-import codechicken.lib.raytracer.CuboidRayTraceResult;
-import codechicken.lib.raytracer.IndexedCuboid6;
-import codechicken.lib.render.CCRenderState;
-import codechicken.lib.render.pipeline.ColourMultiplier;
-import codechicken.lib.render.pipeline.IVertexOperation;
-import codechicken.lib.vec.Cuboid6;
-import codechicken.lib.vec.Matrix4;
-import org.apache.commons.lang3.tuple.Pair;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-import java.util.Random;
-import java.util.function.DoubleSupplier;
-
-import static gregtech.api.capability.GregtechDataCodes.UPDATE_CONTENTS_SEED;
-import static gregtech.api.capability.GregtechDataCodes.UPDATE_LOCKED_STATE;
-
-public class MetaTileEntityLockedSafe extends MetaTileEntity implements IFastRenderMetaTileEntity {
-
-    private static final int MAX_UNLOCK_PROGRESS = 100;
-    private static Component[] ALLOWED_COMPONENTS;
-    private static final IndexedCuboid6 COLLISION_BOX = new IndexedCuboid6(null,
-            new Cuboid6(3 / 16.0, 0 / 16.0, 3 / 16.0, 13 / 16.0, 14 / 16.0, 13 / 16.0));
-
-    private int unlockProgress = -1;
-    private int unlockComponentTier = 1;
-    private boolean isSafeUnlocked = false;
-
-    private long unlockComponentsSeed = 0L;
-    private final ItemStackHandler unlockComponents = new GTItemStackHandler(this, 2);
-    private final ItemStackHandler unlockInventory = new GTItemStackHandler(this, 2) {
-
-        @NotNull
-        @Override
-        public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
-            int maxStackSize = canPutUnlockItemInSlot(slot, stack);
-            if (maxStackSize == 0) return stack;
-            int maxAmount = Math.min(maxStackSize, stack.getCount());
-            int remainder = stack.getCount() - maxAmount;
-            stack = stack.copy();
-            stack.setCount(maxAmount);
-            int addAmount = super.insertItem(slot, stack, simulate).getCount();
-            int totalAmount = remainder + addAmount;
-            return totalAmount == 0 ? ItemStack.EMPTY : GTUtility.copy(totalAmount, stack);
-        }
-
-        @Override
-        protected void onContentsChanged(int slot) {
-            super.onContentsChanged(slot);
-            recheckUnlockItemsAndUnlock();
-        }
-    };
-    private final ItemStackHandler safeLootInventory = new GTItemStackHandler(this, 27);
-    private float doorAngle = 0.0f;
-    private float prevDoorAngle = 0.0f;
-
-    public MetaTileEntityLockedSafe(ResourceLocation metaTileEntityId) {
-        super(metaTileEntityId);
-    }
-
-    @Override
-    public void clearMachineInventory(NonNullList itemBuffer) {
-        if (isSafeUnlocked()) {
-            clearInventory(itemBuffer, safeLootInventory);
-        }
-    }
-
-    @Override
-    public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
-        return new MetaTileEntityLockedSafe(metaTileEntityId);
-    }
-
-    @Override
-    public void update() {
-        super.update();
-        updateOpenVisualAnimation();
-        updateOpenProgress();
-    }
-
-    @Override
-    public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
-                                CuboidRayTraceResult hitResult) {
-        generateUnlockComponents();
-        return super.onRightClick(playerIn, hand, facing, hitResult);
-    }
-
-    @Override
-    public void addCollisionBoundingBox(List collisionList) {
-        collisionList.add(COLLISION_BOX);
-    }
-
-    @Override
-    public boolean shouldDropWhenDestroyed() {
-        return false;
-    }
-
-    @Override
-    public float getBlockHardness() {
-        return isSafeUnlocked() ? 6.0f : -1.0f;
-    }
-
-    @Override
-    public float getBlockResistance() {
-        return 6000000.0F;
-    }
-
-    private void generateUnlockComponents() {
-        if (!getWorld().isRemote && unlockComponentsSeed == 0L) {
-            setUnlockComponentsSeed(new Random().nextLong());
-        }
-    }
-
-    private void updateDisplayUnlockComponents() {
-        GTRecipeInput[] unlockComponents = getUnlockComponents();
-        for (int i = 0; i < Math.min(this.unlockComponents.getSlots(), unlockComponents.length); i++) {
-            if (unlockComponents[i].isOreDict()) {
-                this.unlockComponents.setStackInSlot(i,
-                        OreDictionary.getOres(OreDictionary.getOreName(unlockComponents[i].getOreDict())).get(0));
-            } else {
-                this.unlockComponents.setStackInSlot(i, (unlockComponents[i].getInputStacks()[0]));
-            }
-        }
-    }
-
-    private GTRecipeInput[] getUnlockComponents() {
-        if (ALLOWED_COMPONENTS == null)
-            ALLOWED_COMPONENTS = new Component[] { CraftingComponent.PUMP, CraftingComponent.CONVEYOR,
-                    CraftingComponent.EMITTER, CraftingComponent.SENSOR };
-
-        Random random = new Random(unlockComponentsSeed);
-        return new GTRecipeInput[] {
-                new GTRecipeOreInput(CraftingComponent.CIRCUIT.getIngredient(unlockComponentTier).toString()),
-                new GTRecipeItemInput((ItemStack) ALLOWED_COMPONENTS[random.nextInt(ALLOWED_COMPONENTS.length)]
-                        .getIngredient(unlockComponentTier)),
-        };
-    }
-
-    private void recheckUnlockItemsAndUnlock() {
-        if (getWorld() != null && !getWorld().isRemote && !isSafeUnlocked() &&
-                checkUnlockedItems() && unlockProgress == -1) {
-            this.unlockProgress++;
-            markDirty();
-        }
-    }
-
-    private int canPutUnlockItemInSlot(int slot, ItemStack itemStack) {
-        if (itemStack.isEmpty()) {
-            return 0;
-        }
-        boolean isRequiredItem = false;
-        int amountRequired = 0;
-        GTRecipeInput[] unlockComponents = getUnlockComponents();
-        for (GTRecipeInput stack : unlockComponents) {
-            if (stack == null) continue;
-            if (!stack.acceptsStack(itemStack)) continue;
-            amountRequired = stack.getAmount();
-            isRequiredItem = true;
-            break;
-        }
-        if (!isRequiredItem) {
-            return 0;
-        }
-        for (int i = 0; i < unlockInventory.getSlots(); i++) {
-            ItemStack componentStack = unlockInventory.getStackInSlot(i);
-            if (componentStack.isEmpty()) continue;
-            if (!componentStack.isItemEqual(itemStack)) continue;
-            return slot == i ? amountRequired - componentStack.getCount() : 0;
-        }
-        return amountRequired;
-    }
-
-    private boolean checkUnlockedItems() {
-        GTRecipeInput[] unlockComponents = getUnlockComponents();
-        for (GTRecipeInput stack : unlockComponents) {
-            if (stack == null) continue;
-            int itemLeftToCheck = stack.getAmount();
-            for (int i = 0; i < unlockInventory.getSlots(); i++) {
-                ItemStack otherStack = unlockInventory.getStackInSlot(i);
-                if (otherStack.isEmpty()) continue;
-                if (stack.acceptsStack(otherStack)) continue;
-                itemLeftToCheck -= otherStack.getCount();
-            }
-            if (itemLeftToCheck > 0) return false;
-        }
-        return true;
-    }
-
-    private void updateOpenProgress() {
-        if (!getWorld().isRemote && unlockProgress >= 0 && unlockProgress < MAX_UNLOCK_PROGRESS) {
-            ++this.unlockProgress;
-            if (unlockProgress >= MAX_UNLOCK_PROGRESS) {
-                generateChestContents();
-                setSafeUnlocked(true);
-            }
-        }
-    }
-
-    private void generateChestContents() {
-        ResourceLocation lootTableLocation = GTUtility.gregtechId("chests/abandoned_safe_" + unlockComponentTier);
-        WorldServer worldServer = (WorldServer) getWorld();
-        LootTable lootTable = worldServer.getLootTableManager().getLootTableFromLocation(lootTableLocation);
-        LootContext lootContext = new LootContext.Builder(worldServer).build();
-        Random random = new Random();
-        List loots = lootTable.generateLootForPools(random, lootContext);
-        LootTableHelper.fillInventory(safeLootInventory, random, loots);
-    }
-
-    private void updateOpenVisualAnimation() {
-        this.prevDoorAngle = doorAngle;
-        if ((!isSafeUnlocked && this.doorAngle > 0.0F) ||
-                (isSafeUnlocked && this.doorAngle < 1.0F)) {
-            if (isSafeUnlocked) {
-                this.doorAngle += 0.1F;
-            } else {
-                this.doorAngle -= 0.1F;
-            }
-            if (this.doorAngle > 1.0F) {
-                this.doorAngle = 1.0F;
-            } else if (this.doorAngle < 0.0F) {
-                this.doorAngle = 0.0F;
-            }
-        }
-    }
-
-    public boolean isSafeUnlocked() {
-        return isSafeUnlocked;
-    }
-
-    public void setSafeUnlocked(boolean safeUnlocked) {
-        this.isSafeUnlocked = safeUnlocked;
-        if (getWorld() != null && !getWorld().isRemote) {
-            writeCustomData(UPDATE_LOCKED_STATE, buf -> buf.writeBoolean(safeUnlocked));
-            notifyBlockUpdate();
-            markDirty();
-        }
-    }
-
-    protected void setUnlockComponentsSeed(long unlockComponentsSeed) {
-        this.unlockComponentsSeed = unlockComponentsSeed;
-        if (getWorld() != null && !getWorld().isRemote) {
-            updateDisplayUnlockComponents();
-            writeCustomData(UPDATE_CONTENTS_SEED, buf -> buf.writeVarLong(unlockComponentsSeed));
-            markDirty();
-        }
-    }
-
-    @Override
-    public void writeInitialSyncData(PacketBuffer buf) {
-        super.writeInitialSyncData(buf);
-        buf.writeVarInt(unlockComponentTier);
-        buf.writeBoolean(isSafeUnlocked);
-        buf.writeFloat(doorAngle);
-        buf.writeVarLong(unlockComponentsSeed);
-    }
-
-    @Override
-    public void receiveInitialSyncData(PacketBuffer buf) {
-        super.receiveInitialSyncData(buf);
-        this.unlockComponentTier = buf.readVarInt();
-        this.isSafeUnlocked = buf.readBoolean();
-        this.doorAngle = buf.readFloat();
-        this.prevDoorAngle = doorAngle;
-        this.unlockComponentsSeed = buf.readVarLong();
-    }
-
-    @Override
-    public void receiveCustomData(int dataId, PacketBuffer buf) {
-        super.receiveCustomData(dataId, buf);
-        if (dataId == UPDATE_LOCKED_STATE) {
-            this.isSafeUnlocked = buf.readBoolean();
-        } else if (dataId == UPDATE_CONTENTS_SEED) {
-            this.unlockComponentsSeed = buf.readVarLong();
-        }
-    }
-
-    @Override
-    public void initFromItemStackData(NBTTagCompound itemStack) {
-        super.initFromItemStackData(itemStack);
-        if (itemStack.hasKey("ComponentTier", NBT.TAG_ANY_NUMERIC)) {
-            this.unlockComponentTier = itemStack.getInteger("ComponentTier");
-        }
-    }
-
-    @Override
-    public NBTTagCompound writeToNBT(NBTTagCompound data) {
-        data = super.writeToNBT(data);
-        data.setInteger("UnlockProgress", unlockProgress);
-        data.setInteger("ComponentTier", unlockComponentTier);
-        data.setBoolean("Unlocked", isSafeUnlocked);
-
-        data.setLong("UnlockComponentsSeed", unlockComponentsSeed);
-        data.setTag("UnlockInventory", unlockInventory.serializeNBT());
-        data.setTag("LootInventory", safeLootInventory.serializeNBT());
-        return data;
-    }
-
-    @Override
-    public void readFromNBT(NBTTagCompound data) {
-        super.readFromNBT(data);
-        this.unlockProgress = data.getInteger("UnlockProgress");
-        this.unlockComponentTier = data.getInteger("ComponentTier");
-        this.isSafeUnlocked = data.getBoolean("Unlocked");
-
-        this.unlockComponentsSeed = data.getLong("UnlockComponentsSeed");
-        this.unlockInventory.deserializeNBT(data.getCompoundTag("UnlockInventory"));
-        this.safeLootInventory.deserializeNBT(data.getCompoundTag("LootInventory"));
-        updateDisplayUnlockComponents();
-    }
-
-    @Override
-    public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {}
-
-    @Override
-    public void renderMetaTileEntityFast(CCRenderState renderState, Matrix4 translation, float partialTicks) {
-        ColourMultiplier colourMultiplier = new ColourMultiplier(
-                GTUtility.convertRGBtoOpaqueRGBA_CL(GTValues.VC[unlockComponentTier]));
-        float angle = prevDoorAngle + (doorAngle - prevDoorAngle) * partialTicks;
-        angle = 1.0f - (1.0f - angle) * (1.0f - angle) * (1.0f - angle);
-        float resultDoorAngle = angle * 120.0f;
-        Textures.SAFE.render(renderState, translation, new IVertexOperation[] { colourMultiplier }, getFrontFacing(),
-                resultDoorAngle);
-    }
-
-    @Override
-    public Pair getParticleTexture() {
-        return Pair.of(Textures.SAFE.getParticleTexture(), GTValues.VC[unlockComponentTier]);
-    }
-
-    @Override
-    public boolean isOpaqueCube() {
-        return false;
-    }
-
-    @Override
-    public int getLightOpacity() {
-        return 1;
-    }
-
-    @Override
-    public AxisAlignedBB getRenderBoundingBox() {
-        return new AxisAlignedBB(getPos().add(-1, 0, -1), getPos().add(2, 1, 2));
-    }
-
-    @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
-        DoubleSupplier supplier = () -> 0.2 + (unlockProgress / (MAX_UNLOCK_PROGRESS * 1.0)) * 0.8;
-        ModularUI.Builder builder = ModularUI.defaultBuilder()
-                .widget(new ProgressWidget(supplier, 5, 5, 166, 74,
-                        GuiTextures.PROGRESS_BAR_UNLOCK,
-                        MoveType.VERTICAL_INVERTED))
-                .bindPlayerInventory(entityPlayer.inventory);
-
-        ServerWidgetGroup lockedGroup = new ServerWidgetGroup(() -> !isSafeUnlocked && unlockProgress < 0);
-        lockedGroup.addWidget(new LabelWidget(5, 20, "gregtech.machine.locked_safe.malfunctioning"));
-        lockedGroup.addWidget(new LabelWidget(5, 30, "gregtech.machine.locked_safe.requirements"));
-
-        lockedGroup.addWidget(
-                new SlotWidget(unlockInventory, 0, 70, 40, false, true).setBackgroundTexture(GuiTextures.SLOT));
-        lockedGroup.addWidget(
-                new SlotWidget(unlockInventory, 1, 70 + 18, 40, false, true).setBackgroundTexture(GuiTextures.SLOT));
-
-        lockedGroup.addWidget(new SlotWidget(unlockComponents, 0, 70, 58, false, false));
-        lockedGroup.addWidget(new SlotWidget(unlockComponents, 1, 70 + 18, 58, false, false));
-
-        ServerWidgetGroup unlockedGroup = new ServerWidgetGroup(() -> isSafeUnlocked);
-        for (int row = 0; row < 3; row++) {
-            for (int col = 0; col < 9; col++) {
-                unlockedGroup.addWidget(new SlotWidget(safeLootInventory, col + row * 9,
-                        8 + col * 18, 15 + row * 18, true, false));
-            }
-        }
-
-        return builder.widget(unlockedGroup)
-                .widget(lockedGroup)
-                .build(getHolder(), entityPlayer);
-    }
-
-    @Override
-    public boolean canPlaceCoverOnSide(@NotNull EnumFacing side) {
-        return false;
-    }
-
-    @Override
-    public boolean canRenderMachineGrid(@NotNull ItemStack mainHandStack, @NotNull ItemStack offHandStack) {
-        return false;
-    }
-
-    @Override
-    public boolean showToolUsages() {
-        return false;
-    }
-}
diff --git a/src/main/java/gregtech/common/pipelike/cable/net/EnergyNet.java b/src/main/java/gregtech/common/pipelike/cable/net/EnergyNet.java
index 8d6f7110cb1..a56fb565600 100644
--- a/src/main/java/gregtech/common/pipelike/cable/net/EnergyNet.java
+++ b/src/main/java/gregtech/common/pipelike/cable/net/EnergyNet.java
@@ -11,7 +11,10 @@
 
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 
-import java.util.*;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
 
 public class EnergyNet extends PipeNet {
 
@@ -67,6 +70,11 @@ public void onPipeConnectionsUpdate() {
         NET_DATA.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        NET_DATA.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java b/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
index d54d160e525..befdf615466 100644
--- a/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
+++ b/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
@@ -258,7 +258,7 @@ public  T getCapabilityInternal(Capability capability, @Nullable EnumFacin
         if (capability == GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER) {
             if (world.isRemote)
                 return GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER.cast(clientCapability);
-            if (handlers.size() == 0)
+            if (handlers.isEmpty())
                 initHandlers();
             checkNetwork();
             return GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER.cast(handlers.getOrDefault(facing, defaultHandler));
@@ -293,6 +293,12 @@ private EnergyNet getEnergyNet() {
         return currentEnergyNet;
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     @Override
     public int getDefaultPaintingColor() {
         return 0x404040;
diff --git a/src/main/java/gregtech/common/pipelike/fluidpipe/longdistance/MetaTileEntityLDFluidEndpoint.java b/src/main/java/gregtech/common/pipelike/fluidpipe/longdistance/MetaTileEntityLDFluidEndpoint.java
index 3602e89f3f8..55ddb9d3ba1 100644
--- a/src/main/java/gregtech/common/pipelike/fluidpipe/longdistance/MetaTileEntityLDFluidEndpoint.java
+++ b/src/main/java/gregtech/common/pipelike/fluidpipe/longdistance/MetaTileEntityLDFluidEndpoint.java
@@ -27,6 +27,7 @@
 import codechicken.lib.vec.Matrix4;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class MetaTileEntityLDFluidEndpoint extends MetaTileEntityLongDistanceEndpoint {
@@ -101,7 +102,7 @@ public Pair getParticleTexture() {
 
     private static class FluidHandlerWrapper extends FluidHandlerDelegate {
 
-        public FluidHandlerWrapper(IFluidHandler delegate) {
+        public FluidHandlerWrapper(@NotNull IFluidHandler delegate) {
             super(delegate);
         }
 
diff --git a/src/main/java/gregtech/common/pipelike/itempipe/net/ItemPipeNet.java b/src/main/java/gregtech/common/pipelike/itempipe/net/ItemPipeNet.java
index 25e6534b0f4..c836119c344 100644
--- a/src/main/java/gregtech/common/pipelike/itempipe/net/ItemPipeNet.java
+++ b/src/main/java/gregtech/common/pipelike/itempipe/net/ItemPipeNet.java
@@ -9,7 +9,11 @@
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 
-import java.util.*;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class ItemPipeNet extends PipeNet {
 
@@ -43,6 +47,11 @@ public void onPipeConnectionsUpdate() {
         NET_DATA.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        NET_DATA.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java b/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
index 5880f375964..80b5e233b92 100644
--- a/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
+++ b/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
@@ -153,4 +153,10 @@ public int getTransferredItems() {
         updateTransferredState();
         return this.transferredItems;
     }
+
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
 }
diff --git a/src/main/java/gregtech/common/pipelike/laser/net/LaserPipeNet.java b/src/main/java/gregtech/common/pipelike/laser/net/LaserPipeNet.java
index 02781ae8ce8..67aef2810ac 100644
--- a/src/main/java/gregtech/common/pipelike/laser/net/LaserPipeNet.java
+++ b/src/main/java/gregtech/common/pipelike/laser/net/LaserPipeNet.java
@@ -46,6 +46,11 @@ public void onPipeConnectionsUpdate() {
         netData.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        netData.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java b/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
index 6a11c0503a4..759fa494152 100644
--- a/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
+++ b/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
@@ -224,6 +224,12 @@ public void readFromNBT(@NotNull NBTTagCompound compound) {
         }
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     private static class DefaultLaserContainer implements ILaserContainer {
 
         @Override
diff --git a/src/main/java/gregtech/common/pipelike/optical/net/OpticalPipeNet.java b/src/main/java/gregtech/common/pipelike/optical/net/OpticalPipeNet.java
index 18f2edd36ca..fceabc6aae6 100644
--- a/src/main/java/gregtech/common/pipelike/optical/net/OpticalPipeNet.java
+++ b/src/main/java/gregtech/common/pipelike/optical/net/OpticalPipeNet.java
@@ -47,6 +47,11 @@ public void onPipeConnectionsUpdate() {
         NET_DATA.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        NET_DATA.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java b/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
index adf7f995ae1..43efe04cb56 100644
--- a/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
+++ b/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
@@ -194,6 +194,12 @@ public void receiveCustomData(int discriminator, PacketBuffer buf) {
         }
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     private static class DefaultDataHandler implements IDataAccessHatch {
 
         @Override
diff --git a/src/main/java/gregtech/core/CoreModule.java b/src/main/java/gregtech/core/CoreModule.java
index c063eec5c15..46eeb3e8335 100644
--- a/src/main/java/gregtech/core/CoreModule.java
+++ b/src/main/java/gregtech/core/CoreModule.java
@@ -13,6 +13,9 @@
 import gregtech.api.metatileentity.MetaTileEntityUIFactory;
 import gregtech.api.modules.GregTechModule;
 import gregtech.api.modules.IGregTechModule;
+import gregtech.api.mui.GTGuiTextures;
+import gregtech.api.mui.GTGuiTheme;
+import gregtech.api.mui.GTGuis;
 import gregtech.api.recipes.ModHandler;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.recipes.recipeproperties.TemperatureProperty;
@@ -25,6 +28,7 @@
 import gregtech.api.util.CapesRegistry;
 import gregtech.api.util.VirtualTankRegistry;
 import gregtech.api.util.input.KeyBind;
+import gregtech.api.util.oreglob.OreGlob;
 import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinHandler;
 import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinSaveData;
 import gregtech.api.worldgen.config.WorldGenRegistry;
@@ -40,6 +44,7 @@
 import gregtech.common.command.worldgen.CommandWorldgen;
 import gregtech.common.covers.CoverBehaviors;
 import gregtech.common.covers.filter.FilterTypeRegistry;
+import gregtech.common.covers.filter.oreglob.impl.OreGlobParser;
 import gregtech.common.items.MetaItems;
 import gregtech.common.items.ToolItems;
 import gregtech.common.metatileentities.MetaTileEntities;
@@ -111,6 +116,8 @@ public CoreModule() {
         // must be set here because of GroovyScript compat
         // trying to read this before the pre-init stage
         GregTechAPI.materialManager = MaterialRegistryManager.getInstance();
+
+        OreGlob.setCompiler((expr, ignoreCase) -> new OreGlobParser(expr, ignoreCase).compile());
     }
 
     @NotNull
@@ -128,6 +135,11 @@ public void preInit(FMLPreInitializationEvent event) {
         GregTechAPI.soundManager = SoundManager.getInstance();
         GTSoundEvents.register();
 
+        /* MUI Initialization */
+        GTGuis.registerFactories();
+        GTGuiTextures.init();
+        GTGuiTheme.registerThemes();
+
         /* Start UI Factory Registration */
         UI_FACTORY_REGISTRY.unfreeze();
         logger.info("Registering GTCEu UI Factories");
diff --git a/src/main/java/gregtech/core/network/internal/NetworkHandler.java b/src/main/java/gregtech/core/network/internal/NetworkHandler.java
index 60849c35415..3d6769891f9 100644
--- a/src/main/java/gregtech/core/network/internal/NetworkHandler.java
+++ b/src/main/java/gregtech/core/network/internal/NetworkHandler.java
@@ -7,6 +7,7 @@
 import gregtech.api.network.INetworkHandler;
 import gregtech.api.network.IPacket;
 import gregtech.api.network.IServerExecutor;
+import gregtech.api.util.GTLog;
 import gregtech.core.CoreModule;
 
 import net.minecraft.client.network.NetHandlerPlayClient;
@@ -26,8 +27,11 @@
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import io.netty.buffer.Unpooled;
+import org.jetbrains.annotations.NotNull;
 
-public class NetworkHandler implements INetworkHandler {
+import java.lang.reflect.InvocationTargetException;
+
+public final class NetworkHandler implements INetworkHandler {
 
     private static final NetworkHandler INSTANCE = new NetworkHandler();
 
@@ -106,7 +110,7 @@ public void sendToServer(IPacket packet) {
 
     @SubscribeEvent
     @SideOnly(Side.CLIENT)
-    public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) throws Exception {
+    public void onClientPacket(FMLNetworkEvent.@NotNull ClientCustomPacketEvent event) throws Exception {
         IPacket packet = toGTPacket(event.getPacket());
         if (IClientExecutor.class.isAssignableFrom(packet.getClass())) {
             IClientExecutor executor = (IClientExecutor) packet;
@@ -121,7 +125,7 @@ public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) throws
     }
 
     @SubscribeEvent
-    public void onServerPacket(FMLNetworkEvent.ServerCustomPacketEvent event) throws Exception {
+    public void onServerPacket(FMLNetworkEvent.@NotNull ServerCustomPacketEvent event) throws Exception {
         IPacket packet = toGTPacket(event.getPacket());
         if (IServerExecutor.class.isAssignableFrom(packet.getClass())) {
             IServerExecutor executor = (IServerExecutor) packet;
@@ -135,17 +139,27 @@ public void onServerPacket(FMLNetworkEvent.ServerCustomPacketEvent event) throws
         }
     }
 
-    private FMLProxyPacket toFMLPacket(IPacket packet) {
+    private @NotNull FMLProxyPacket toFMLPacket(@NotNull IPacket packet) {
         PacketBuffer buf = new PacketBuffer(Unpooled.buffer());
         buf.writeVarInt(packetHandler.getPacketId(packet.getClass()));
         packet.encode(buf);
         return new FMLProxyPacket(buf, GTValues.MODID);
     }
 
-    private IPacket toGTPacket(FMLProxyPacket proxyPacket) throws Exception {
+    private @NotNull IPacket toGTPacket(@NotNull FMLProxyPacket proxyPacket) throws NoSuchMethodException,
+                                                                             InvocationTargetException,
+                                                                             InstantiationException,
+                                                                             IllegalAccessException {
         PacketBuffer payload = (PacketBuffer) proxyPacket.payload();
-        IPacket packet = packetHandler.getPacketClass(payload.readVarInt()).newInstance();
+        var clazz = packetHandler.getPacketClass(payload.readVarInt());
+        IPacket packet = clazz.getConstructor().newInstance();
         packet.decode(payload);
+
+        if (payload.readableBytes() != 0) {
+            GTLog.logger.error(
+                    "NetworkHandler failed to finish reading packet with class {} and {} bytes remaining",
+                    clazz.getName(), payload.readableBytes());
+        }
         return packet;
     }
 }
diff --git a/src/main/java/gregtech/integration/chisel/ChiselModule.java b/src/main/java/gregtech/integration/chisel/ChiselModule.java
new file mode 100644
index 00000000000..c6dc3cafe05
--- /dev/null
+++ b/src/main/java/gregtech/integration/chisel/ChiselModule.java
@@ -0,0 +1,119 @@
+package gregtech.integration.chisel;
+
+import gregtech.api.GTValues;
+import gregtech.api.block.VariantBlock;
+import gregtech.api.modules.GregTechModule;
+import gregtech.api.unification.material.Material;
+import gregtech.api.unification.material.Materials;
+import gregtech.common.blocks.BlockColored;
+import gregtech.common.blocks.BlockCompressed;
+import gregtech.common.blocks.BlockWarningSign;
+import gregtech.common.blocks.BlockWarningSign1;
+import gregtech.common.blocks.MetaBlocks;
+import gregtech.common.blocks.StoneVariantBlock;
+import gregtech.common.blocks.StoneVariantBlock.StoneType;
+import gregtech.common.blocks.StoneVariantBlock.StoneVariant;
+import gregtech.common.blocks.wood.BlockGregPlanks;
+import gregtech.integration.IntegrationSubmodule;
+import gregtech.modules.GregTechModules;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.EnumDyeColor;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.IStringSerializable;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLInterModComms;
+
+import team.chisel.common.carving.Carving;
+
+import java.util.Objects;
+
+@GregTechModule(
+                moduleID = GregTechModules.MODULE_CHISEL,
+                containerID = GTValues.MODID,
+                modDependencies = GTValues.MODID_CHISEL,
+                name = "GregTech Chisel Integration",
+                description = "Chisel Integration Module")
+public class ChiselModule extends IntegrationSubmodule {
+
+    @Override
+    public void init(FMLInitializationEvent event) {
+        // GT custom groups
+        addVariations("gt_warning_sign", MetaBlocks.WARNING_SIGN, BlockWarningSign.SignType.values());
+        addVariations("gt_warning_sign", MetaBlocks.WARNING_SIGN_1, BlockWarningSign1.SignType.values());
+        addVariations("gt_studs", MetaBlocks.STUDS);
+        addVariations("gt_metal_sheet", MetaBlocks.METAL_SHEET);
+        addVariations("gt_large_metal_sheet", MetaBlocks.LARGE_METAL_SHEET);
+        for (EnumDyeColor color : EnumDyeColor.values()) {
+            Block lamp = MetaBlocks.LAMPS.get(color);
+            Block lampBorderless = MetaBlocks.BORDERLESS_LAMPS.get(color);
+            String group = "gt_lamp_" + color.getName().toLowerCase();
+            for (int i = 0; i < 8; i++) {
+                addVariation(group, lamp, i);
+                addVariation(group, lampBorderless, i);
+            }
+        }
+
+        // Chisel shared groups
+        addVariations("marble", StoneType.MARBLE, false);
+        addVariations("basalt", StoneType.BASALT, false);
+        addVariations("black_granite", StoneType.BLACK_GRANITE, false);
+        addVariations("red_granite", StoneType.RED_GRANITE, false);
+        addVariations("light_concrete", StoneType.CONCRETE_LIGHT, true);
+        addVariations("dark_concrete", StoneType.CONCRETE_DARK, true);
+
+        // Mod-dependent groups
+        if (doesGroupExist("treated_wood")) { // IE Treated Wood group
+            addVariations("treated_wood", MetaBlocks.PLANKS, BlockGregPlanks.BlockType.TREATED_PLANK);
+        }
+        if (doesGroupExist("certus")) { // AE2 Certus Quartz group
+            addVariation("certus", Materials.CertusQuartz);
+        }
+    }
+
+    @SafeVarargs
+    private  & IStringSerializable, T extends VariantBlock> void addVariations(String group,
+                                                                                                    T block,
+                                                                                                    U... variants) {
+        if (variants != null) {
+            for (U variant : variants) {
+                addVariation(group, block, block.getMetaFromState(block.getState(variant)));
+            }
+        }
+    }
+
+    private void addVariations(String group, BlockColored block) {
+        for (EnumDyeColor color : EnumDyeColor.values()) {
+            addVariation(group, block, color.getMetadata());
+        }
+    }
+
+    private void addVariations(String group, StoneType type, boolean enableCobbles) {
+        for (StoneVariantBlock.StoneVariant variant : StoneVariant.values()) {
+            if (!enableCobbles && (variant == StoneVariant.COBBLE || variant == StoneVariant.COBBLE_MOSSY)) {
+                continue;
+            }
+            StoneVariantBlock block = MetaBlocks.STONE_BLOCKS.get(variant);
+            int meta = block.getMetaFromState(block.getState(type));
+            addVariation(group, block, meta);
+        }
+    }
+
+    private void addVariation(String group, Material material) {
+        BlockCompressed block = MetaBlocks.COMPRESSED.get(material);
+        int meta = block.getMetaFromState(block.getBlock(material));
+        addVariation(group, block, meta);
+    }
+
+    private void addVariation(String group, Block block, int meta) {
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setString("group", group);
+        tag.setString("block", Objects.requireNonNull(block.getRegistryName()).toString());
+        tag.setInteger("meta", meta);
+        FMLInterModComms.sendMessage(GTValues.MODID_CHISEL, "add_variation", tag);
+    }
+
+    private boolean doesGroupExist(String group) {
+        return Carving.chisel.getGroup(group) != null;
+    }
+}
diff --git a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
index 751be8aa3ef..ca5d08a1efa 100644
--- a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
+++ b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
@@ -22,7 +22,7 @@ protected static FluidState validateFluidState(String fluidTypeName) {
         if (fluidTypeName.equals("gas")) return FluidState.GAS;
         if (fluidTypeName.equals("plasma")) return FluidState.PLASMA;
 
-        String message = "Fluid Type must be either \"liquid\", \"gas\", or \"acid\"!";
+        String message = "Fluid Type must be either \"liquid\", \"gas\", or \"plasma\"!";
         CraftTweakerAPI.logError(message);
         throw new IllegalArgumentException(message);
     }
diff --git a/src/main/java/gregtech/integration/crafttweaker/material/MaterialPropertyExpansion.java b/src/main/java/gregtech/integration/crafttweaker/material/MaterialPropertyExpansion.java
index 0f2c7a3d882..bf4624282cb 100644
--- a/src/main/java/gregtech/integration/crafttweaker/material/MaterialPropertyExpansion.java
+++ b/src/main/java/gregtech/integration/crafttweaker/material/MaterialPropertyExpansion.java
@@ -156,20 +156,27 @@ public static void addFluid(Material m) {
     public static void addFluid(Material m, @Optional String fluidTypeName, @Optional boolean hasBlock) {
         if (checkFrozen("add a Fluid to a material")) return;
         FluidState type = validateFluidState(fluidTypeName);
-        if (m.hasProperty(PropertyKey.FLUID)) {
-            FluidStorage storage = m.getProperty(PropertyKey.FLUID).getStorage();
-            FluidBuilder builder = new FluidBuilder();
-            if (hasBlock) builder.block();
+        FluidProperty property = m.getProperty(PropertyKey.FLUID);
+        if (property == null) {
+            property = new FluidProperty();
+            m.setProperty(PropertyKey.FLUID, property);
+        }
+
+        FluidStorage storage = property.getStorage();
+        FluidBuilder builder = switch (type) {
+            case LIQUID -> storage.getQueuedBuilder(FluidStorageKeys.LIQUID);
+            case GAS -> storage.getQueuedBuilder(FluidStorageKeys.GAS);
+            case PLASMA -> storage.getQueuedBuilder(FluidStorageKeys.PLASMA);
+        };
+        if (builder == null) {
+            builder = new FluidBuilder();
             switch (type) {
                 case LIQUID -> storage.enqueueRegistration(FluidStorageKeys.LIQUID, builder);
                 case GAS -> storage.enqueueRegistration(FluidStorageKeys.GAS, builder.state(FluidState.GAS));
                 case PLASMA -> storage.enqueueRegistration(FluidStorageKeys.PLASMA, builder.state(FluidState.PLASMA));
             }
-        } else {
-            FluidProperty property = new FluidProperty();
-            property.getStorage().enqueueRegistration(FluidStorageKeys.LIQUID, new FluidBuilder());
-            m.setProperty(PropertyKey.FLUID, property);
         }
+        if (hasBlock) builder.block();
     }
 
     @ZenMethod
diff --git a/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java b/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
index 827eb307c28..97fed7a25c3 100644
--- a/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
+++ b/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
@@ -587,7 +587,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, TOLERATES_RAIN, true);
             },
             dis -> {
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
+                if (Loader.isModLoaded(GTValues.MODID_MB) && Loader.isModLoaded(GTValues.MODID_APPENG)) {
+                    // MB Skystone bee is only registered if AE2 is also active
                     dis.registerMutation(IRON, ForestryUtil.getSpecies(GTValues.MODID_MB, "AESkystone"), 17);
                 } else {
                     dis.registerMutation(IRON, BeeDefinition.IMPERIAL, 17);
diff --git a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
index b6c1d9519bf..be7c1bd50ca 100644
--- a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
+++ b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
@@ -28,9 +28,20 @@
 import gregtech.common.items.MetaItems;
 import gregtech.common.metatileentities.MetaTileEntities;
 import gregtech.integration.IntegrationSubmodule;
-import gregtech.integration.jei.basic.*;
+import gregtech.integration.jei.basic.GTFluidVeinCategory;
+import gregtech.integration.jei.basic.GTFluidVeinInfo;
+import gregtech.integration.jei.basic.GTOreCategory;
+import gregtech.integration.jei.basic.GTOreInfo;
+import gregtech.integration.jei.basic.MaterialTree;
+import gregtech.integration.jei.basic.MaterialTreeCategory;
+import gregtech.integration.jei.basic.OreByProduct;
+import gregtech.integration.jei.basic.OreByProductCategory;
 import gregtech.integration.jei.multiblock.MultiblockInfoCategory;
-import gregtech.integration.jei.recipe.*;
+import gregtech.integration.jei.recipe.FacadeRegistryPlugin;
+import gregtech.integration.jei.recipe.GTRecipeWrapper;
+import gregtech.integration.jei.recipe.IntCircuitCategory;
+import gregtech.integration.jei.recipe.IntCircuitRecipeWrapper;
+import gregtech.integration.jei.recipe.RecipeMapCategory;
 import gregtech.integration.jei.utils.MachineSubtypeHandler;
 import gregtech.integration.jei.utils.MetaItemSubtypeHandler;
 import gregtech.integration.jei.utils.ModularUIGuiHandler;
@@ -46,7 +57,13 @@
 import net.minecraftforge.fml.relauncher.Side;
 
 import mezz.jei.Internal;
-import mezz.jei.api.*;
+import mezz.jei.api.IGuiHelper;
+import mezz.jei.api.IJeiHelpers;
+import mezz.jei.api.IJeiRuntime;
+import mezz.jei.api.IModPlugin;
+import mezz.jei.api.IModRegistry;
+import mezz.jei.api.ISubtypeRegistry;
+import mezz.jei.api.JEIPlugin;
 import mezz.jei.api.ingredients.IIngredientRegistry;
 import mezz.jei.api.ingredients.VanillaTypes;
 import mezz.jei.api.recipe.IRecipeCategoryRegistration;
@@ -105,7 +122,7 @@ public void registerCategories(@NotNull IRecipeCategoryRegistration registry) {
         registry.addRecipeCategories(new IntCircuitCategory(registry.getJeiHelpers().getGuiHelper()));
         registry.addRecipeCategories(new MultiblockInfoCategory(registry.getJeiHelpers()));
         for (RecipeMap recipeMap : RecipeMap.getRecipeMaps()) {
-            if (!recipeMap.isHidden) {
+            if (recipeMap.getRecipeMapUI().isJEIVisible()) {
                 for (GTRecipeCategory category : recipeMap.getRecipesByCategory().keySet()) {
                     registry.addRecipeCategories(
                             new RecipeMapCategory(recipeMap, category, registry.getJeiHelpers().getGuiHelper()));
@@ -146,7 +163,7 @@ public void register(IModRegistry registry) {
                 VanillaRecipeCategoryUid.CRAFTING);
 
         for (RecipeMap recipeMap : RecipeMap.getRecipeMaps()) {
-            if (!recipeMap.isHidden) {
+            if (recipeMap.getRecipeMapUI().isJEIVisible()) {
                 for (Map.Entry> entry : recipeMap.getRecipesByCategory().entrySet()) {
                     Stream recipeStream = entry.getValue().stream()
                             .filter(recipe -> !recipe.isHidden() && recipe.hasValidInputsForDisplay());
diff --git a/src/main/java/gregtech/integration/jei/recipe/RecipeMapCategory.java b/src/main/java/gregtech/integration/jei/recipe/RecipeMapCategory.java
index ce0c074e933..52039a21b07 100644
--- a/src/main/java/gregtech/integration/jei/recipe/RecipeMapCategory.java
+++ b/src/main/java/gregtech/integration/jei/recipe/RecipeMapCategory.java
@@ -70,7 +70,7 @@ public RecipeMapCategory(@NotNull RecipeMap recipeMap, @NotNull GTRecipeCateg
         FluidTank[] exportFluidTanks = new FluidTank[recipeMap.getMaxFluidOutputs()];
         for (int i = 0; i < exportFluidTanks.length; i++)
             exportFluidTanks[i] = new FluidTank(16000);
-        this.modularUI = recipeMap.createJeiUITemplate(
+        this.modularUI = recipeMap.getRecipeMapUI().createJeiUITemplate(
                 (importItems = new ItemStackHandler(
                         recipeMap.getMaxInputs() + (recipeMap == RecipeMaps.ASSEMBLY_LINE_RECIPES ? 1 : 0))),
                 (exportItems = new ItemStackHandler(recipeMap.getMaxOutputs())),
@@ -79,7 +79,7 @@ public RecipeMapCategory(@NotNull RecipeMap recipeMap, @NotNull GTRecipeCateg
                 .build(new BlankUIHolder(), Minecraft.getMinecraft().player);
         this.modularUI.initWidgets();
         this.backgroundDrawable = guiHelper.createBlankDrawable(modularUI.getWidth(),
-                modularUI.getHeight() * 2 / 3 + recipeMap.getPropertyHeightShift());
+                modularUI.getHeight() * 2 / 3 + recipeMap.getRecipeMapUI().getPropertyHeightShift());
         gtCategories.put(category, this);
         recipeMapCategories.compute(recipeMap, (k, v) -> {
             if (v == null) v = new ArrayList<>();
diff --git a/src/main/java/gregtech/loaders/OreDictionaryLoader.java b/src/main/java/gregtech/loaders/OreDictionaryLoader.java
index b05fba4609b..a902983af39 100644
--- a/src/main/java/gregtech/loaders/OreDictionaryLoader.java
+++ b/src/main/java/gregtech/loaders/OreDictionaryLoader.java
@@ -25,12 +25,14 @@ public class OreDictionaryLoader {
 
     public static final String OREDICT_FUEL_COKE = "fuelCoke";
     public static final String OREDICT_BLOCK_FUEL_COKE = "blockFuelCoke";
+    public static final String OREDICT_BLOCK_COAL_COKE = "blockCoalCoke";
 
     public static void init() {
         GTLog.logger.info("Registering OreDict entries.");
 
         OreDictionary.registerOre(OREDICT_FUEL_COKE, OreDictUnifier.get(OrePrefix.gem, Materials.Coke));
         OreDictionary.registerOre(OREDICT_BLOCK_FUEL_COKE, OreDictUnifier.get(OrePrefix.block, Materials.Coke));
+        OreDictionary.registerOre(OREDICT_BLOCK_COAL_COKE, OreDictUnifier.get(OrePrefix.block, Materials.Coke));
         OreDictionary.registerOre("crystalCertusQuartz", OreDictUnifier.get(OrePrefix.gem, Materials.CertusQuartz));
 
         OreDictUnifier.registerOre(new ItemStack(Blocks.CLAY), OrePrefix.block, Materials.Clay);
diff --git a/src/main/java/gregtech/loaders/recipe/FuelRecipes.java b/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
index 94da5ecb799..740521e8657 100644
--- a/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
@@ -291,6 +291,13 @@ public static void registerFuels() {
         RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
                 .fluidInputs(Iron.getPlasma(1))
                 .fluidOutputs(Iron.getFluid(1))
+                .duration(112)
+                .EUt((int) V[EV])
+                .buildAndRegister();
+
+        RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
+                .fluidInputs(Tin.getPlasma(1))
+                .fluidOutputs(Tin.getFluid(1))
                 .duration(128)
                 .EUt((int) V[EV])
                 .buildAndRegister();
@@ -301,5 +308,12 @@ public static void registerFuels() {
                 .duration(192)
                 .EUt((int) V[EV])
                 .buildAndRegister();
+
+        RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
+                .fluidInputs(Americium.getPlasma(1))
+                .fluidOutputs(Americium.getFluid(1))
+                .duration(320)
+                .EUt((int) V[EV])
+                .buildAndRegister();
     }
 }
diff --git a/src/main/java/gregtech/loaders/recipe/FusionLoader.java b/src/main/java/gregtech/loaders/recipe/FusionLoader.java
index f5ec3a7b861..49515136876 100644
--- a/src/main/java/gregtech/loaders/recipe/FusionLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/FusionLoader.java
@@ -38,16 +38,16 @@ public static void init() {
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Silicon.getFluid(16))
                 .fluidInputs(Materials.Magnesium.getFluid(16))
-                .fluidOutputs(Materials.Iron.getPlasma(16))
+                .fluidOutputs(Materials.Iron.getPlasma(144))
                 .duration(32)
                 .EUt(VA[IV])
-                .EUToStart(360_000_000)
+                .EUToStart(300_000_000)
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Potassium.getFluid(16))
                 .fluidInputs(Materials.Fluorine.getFluid(125))
-                .fluidOutputs(Materials.Nickel.getPlasma(16))
+                .fluidOutputs(Materials.Nickel.getPlasma(144))
                 .duration(16)
                 .EUt(VA[LuV])
                 .EUToStart(480_000_000)
@@ -62,6 +62,24 @@ public static void init() {
                 .EUToStart(180_000_000)
                 .buildAndRegister();
 
+        RecipeMaps.FUSION_RECIPES.recipeBuilder()
+                .fluidInputs(Materials.Plutonium241.getFluid(144))
+                .fluidInputs(Materials.Hydrogen.getFluid(2000))
+                .fluidOutputs(Materials.Americium.getPlasma(144))
+                .duration(64)
+                .EUt(98304)
+                .EUToStart(500_000_000)
+                .buildAndRegister();
+
+        RecipeMaps.FUSION_RECIPES.recipeBuilder()
+                .fluidInputs(Materials.Silver.getFluid(144))
+                .fluidInputs(Materials.Helium3.getFluid(375))
+                .fluidOutputs(Materials.Tin.getPlasma(144))
+                .duration(16)
+                .EUt(49152)
+                .EUToStart(280_000_000)
+                .buildAndRegister();
+
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Neodymium.getFluid(16))
                 .fluidInputs(Materials.Hydrogen.getFluid(375))
@@ -72,9 +90,9 @@ public static void init() {
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
-                .fluidInputs(Materials.Lutetium.getFluid(32))
-                .fluidInputs(Materials.Chrome.getFluid(32))
-                .fluidOutputs(Materials.Americium.getFluid(32))
+                .fluidInputs(Materials.Lutetium.getFluid(16))
+                .fluidInputs(Materials.Chrome.getFluid(16))
+                .fluidOutputs(Materials.Americium.getFluid(16))
                 .duration(64)
                 .EUt(49152)
                 .EUToStart(200_000_000)
@@ -147,16 +165,16 @@ public static void init() {
                 .fluidInputs(Materials.Gallium.getFluid(16))
                 .fluidInputs(Materials.Radon.getFluid(125))
                 .fluidOutputs(Materials.Duranium.getFluid(16))
-                .duration(64)
+                .duration(32)
                 .EUt(16384)
                 .EUToStart(140_000_000)
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
-                .fluidInputs(Materials.Titanium.getFluid(32))
+                .fluidInputs(Materials.Titanium.getFluid(48))
                 .fluidInputs(Materials.Duranium.getFluid(32))
                 .fluidOutputs(Materials.Tritanium.getFluid(16))
-                .duration(64)
+                .duration(16)
                 .EUt(VA[LuV])
                 .EUToStart(200_000_000)
                 .buildAndRegister();
diff --git a/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java b/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
index e4cbbab3310..ba4223d289f 100644
--- a/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
@@ -140,7 +140,10 @@ private static void registerExtractorRecycling(ItemStack input, List ms.material.hasProperty(PropertyKey.FLUID)).findFirst()
-                .orElse(null);
+        MaterialStack fluidMs = materials.stream()
+                .filter(ms -> ms.material.hasProperty(PropertyKey.FLUID) && ms.material.getFluid() != null)
+                .findFirst().orElse(null);
         if (fluidMs == null) return;
 
         // Find the next MaterialStack, which will be the Item output.
diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
index 3d51fc11124..fc433308a0d 100644
--- a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
@@ -369,44 +369,6 @@ public static void init() {
                 .output(dust, Carbon, 4)
                 .duration(100).EUt(60).buildAndRegister();
 
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(AceticAcid.getFluid(2000))
-                .fluidOutputs(Ethane.getFluid(1000))
-                .fluidOutputs(CarbonDioxide.getFluid(2000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(512).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Chloromethane.getFluid(2000))
-                .fluidOutputs(Ethane.getFluid(1000))
-                .fluidOutputs(Chlorine.getFluid(2000))
-                .duration(400).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Acetone.getFluid(2000))
-                .output(dust, Carbon, 3)
-                .fluidOutputs(Propane.getFluid(1000))
-                .fluidOutputs(Water.getFluid(2000))
-                .duration(480).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Butane.getFluid(1000))
-                .fluidOutputs(Butene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(240).EUt(VA[MV]).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Butene.getFluid(1000))
-                .fluidOutputs(Butadiene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(240).EUt(VA[MV]).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Propane.getFluid(1000))
-                .fluidOutputs(Propene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(640).EUt(VA[MV]).buildAndRegister();
-
         ELECTROLYZER_RECIPES.recipeBuilder()
                 .input(dust, Diamond)
                 .output(dust, Carbon, 64)
@@ -470,6 +432,92 @@ public static void init() {
                 .fluidOutputs(Chlorine.getFluid(1000))
                 .duration(288).EUt(60).buildAndRegister();
 
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Propane.getFluid(1000))
+                .output(dust, Carbon, 3)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(176).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butene.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(192).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butane.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(10000))
+                .duration(224).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Styrene.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(384).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butadiene.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(240).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Phenol.getFluid(1000))
+                .output(dust, Carbon, 6)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .fluidOutputs(Oxygen.getFluid(1000))
+                .duration(312).EUt(90).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethylene.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(4000))
+                .duration(96).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Benzene.getFluid(1000))
+                .output(dust, Carbon, 6)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(288).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethanol.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .fluidOutputs(Oxygen.getFluid(1000))
+                .duration(144).EUt(90).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Toluene.getFluid(1000))
+                .output(dust, Carbon, 7)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(360).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Dimethylbenzene.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(10000))
+                .duration(432).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Octane.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(18000))
+                .duration(624).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Propene.getFluid(1000))
+                .output(dust, Carbon, 3)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(144).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethane.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(128).EUt(60).buildAndRegister();
+
         // Thermal Centrifuge
         THERMAL_CENTRIFUGE_RECIPES.recipeBuilder()
                 .inputs(new ItemStack(Blocks.COBBLESTONE, 1, GTValues.W))
diff --git a/src/main/java/gregtech/modules/GregTechModules.java b/src/main/java/gregtech/modules/GregTechModules.java
index 9c07e90db26..96ad33664b9 100644
--- a/src/main/java/gregtech/modules/GregTechModules.java
+++ b/src/main/java/gregtech/modules/GregTechModules.java
@@ -17,6 +17,7 @@ public class GregTechModules implements IModuleContainer {
     public static final String MODULE_HWYLA = "hwyla_integration";
     public static final String MODULE_BAUBLES = "baubles_integration";
     public static final String MODULE_FR = "fr_integration";
+    public static final String MODULE_CHISEL = "chisel_integration";
 
     @Override
     public String getID() {
diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang
index 072425d3337..00eb3d66c49 100644
--- a/src/main/resources/assets/gregtech/lang/en_us.lang
+++ b/src/main/resources/assets/gregtech/lang/en_us.lang
@@ -45,8 +45,8 @@ gregtech.multiblock.steam.low_steam=Not enough Steam to run!
 gregtech.multiblock.steam.steam_stored=Steam: %s
 gregtech.machine.steam_hatch.name=Steam Hatch
 gregtech.machine.steam.steam_hatch.tooltip=§eAccepted Fluid: §fSteam
-gregtech.machine.steam_import_bus.name=Input Bus (Steam)
-gregtech.machine.steam_export_bus.name=Output Bus (Steam)
+gregtech.machine.steam_import_bus.name=Steam Input Bus
+gregtech.machine.steam_export_bus.name=Steam Output Bus
 gregtech.machine.steam_bus.tooltip=Does not work with non-steam multiblocks
 gregtech.machine.steam_oven.name=Steam Oven
 gregtech.multiblock.steam_oven.description=A Multi Smelter at the Steam Age. Requires at least 6 Bronze Casings to form. Cannot use normal Input/Output busses, nor Fluid Hatches other than the Steam Hatch. Steam Hatch must be on the bottom layer, no more than one.
@@ -531,7 +531,7 @@ metaitem.tool.datamodule.name=Data Module
 metaitem.tool.datamodule.tooltip=Storage for Incredibly Complex Data/n§cCan only be read by a Data Bank
 metaitem.circuit.integrated.name=Programmed Circuit
 metaitem.circuit.integrated.tooltip=Use to open configuration GUI/n/nShift-Right-Click on a machine/nwith a circuit slot to set it to/nthis circuit's value./n
-metaitem.circuit.integrated.gui=Programmed Circuit Configuration
+metaitem.circuit.integrated.gui=Circuit Configuration
 metaitem.circuit.integrated.jei_description=JEI is only showing recipes for the given configuration.\n\nYou can select a configuration in the Programmed Circuit configuration tab.
 
 item.glass.lens=Glass Lens (White)
@@ -1175,7 +1175,7 @@ cover.filter.blacklist.disabled=Whitelist
 cover.filter.blacklist.enabled=Blacklist
 
 cover.ore_dictionary_filter.title=Ore Dictionary Filter
-cover.ore_dictionary_filter.info=§bAccepts complex expressions/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r for grouping/n§6*§r for wildcard (i.e. 0 or more characters)/n§6?§r for any 1 character/n§6()§r for matching empty entry (including items with no ore dictionary)/n§6$c§r at the start of expression for case-sensitive match/n§bExample:/n§6dust*Gold | (plate* & !*Double*)/nWill match all gold dusts of all sizes or all plates, but not double plates
+cover.ore_dictionary_filter.info=§bAccepts complex expressions/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r for grouping/n§6*§r for wildcard (i.e. 0 or more characters)/n§6?§r for any 1 character/n§6()§r for matching empty entry (including items with no ore dictionary)/n§bExample:/n§6dust*Gold | (plate* & !*Double*)/nWill match all gold dusts of all sizes or all plates, but not double plates
 cover.ore_dictionary_filter.test_slot.info=Insert a item to test if it matches the filter expression
 cover.ore_dictionary_filter.test_slot.matches=§a* %s
 cover.ore_dictionary_filter.test_slot.matches_not=§c* %s
@@ -1186,6 +1186,10 @@ cover.ore_dictionary_filter.status.err_warn=§c%s error(s) and %s warning(s)
 cover.ore_dictionary_filter.status.warn=§7%s warning(s)
 cover.ore_dictionary_filter.status.no_issues=§aNo issues
 cover.ore_dictionary_filter.status.explain=Ore filter explanation:
+cover.ore_dictionary_filter.button.case_sensitive.disabled=Case insensitive match
+cover.ore_dictionary_filter.button.case_sensitive.enabled=Case sensitive match
+cover.ore_dictionary_filter.button.match_all.disabled=Match any one of ore dictionary entries
+cover.ore_dictionary_filter.button.match_all.enabled=Match all ore dictionary entries
 
 cover.ore_dictionary_filter.preview.next=... followed by 
 cover.ore_dictionary_filter.preview.match='%s'
@@ -2718,11 +2722,6 @@ gregtech.machine.crate.titanium.name=Titanium Crate
 gregtech.machine.crate.tungstensteel.name=Tungstensteel Crate
 gregtech.crate.tooltip.taped_movement=Can be Taped to keep inventory contents when broken
 
-# Safe
-gregtech.machine.locked_safe.name=Locked Safe
-gregtech.machine.locked_safe.malfunctioning=§cMalfunctioning!
-gregtech.machine.locked_safe.requirements=§7Replacements required:
-
 # Workbench
 gregtech.machine.workbench.name=Crafting Station
 gregtech.machine.workbench.tooltip1=Better than Forestry
@@ -4959,7 +4958,6 @@ gregtech.machine.item_bus.export.uhv.name=UHV Output Bus
 
 gregtech.bus.collapse_true=Bus will collapse Items
 gregtech.bus.collapse_false=Bus will not collapse Items
-gregtech.bus.collapse.error=Bus must be attached to multiblock first
 
 gregtech.machine.fluid_hatch.import.tooltip=Fluid Input for Multiblocks
 
@@ -5392,9 +5390,12 @@ gregtech.gui.fluid_auto_output.tooltip.enabled=Fluid Auto-Output Enabled
 gregtech.gui.fluid_auto_output.tooltip.disabled=Fluid Auto-Output Disabled
 gregtech.gui.item_auto_output.tooltip.enabled=Item Auto-Output Enabled
 gregtech.gui.item_auto_output.tooltip.disabled=Item Auto-Output Disabled
+gregtech.gui.item_auto_collapse.tooltip.enabled=Item Auto-Collapse Enabled
+gregtech.gui.item_auto_collapse.tooltip.disabled=Item Auto-Collapse Disabled
 gregtech.gui.charger_slot.tooltip=§fCharger Slot§r/n§7Draws power from %s batteries§r/n§7Charges %s tools and batteries
-gregtech.gui.configurator_slot.tooltip=§fProgrammed Circuit Slot§r/n§aConfigured Value: §f%d§7/n/n§7LMB/RMB/Scroll to cycle through the list/n§7Shift-right-click to clear
+gregtech.gui.configurator_slot.tooltip=§fProgrammed Circuit Slot§r\n§aConfigured Value: §f%d§7\n\n§7LMB/RMB/Scroll to cycle through the list\n§7Shift-left-click to open selector\n§7Shift-right-click to clear
 gregtech.gui.configurator_slot.no_value=None
+gregtech.gui.configurator_slot.unavailable.tooltip=Programmed Circuit slot unavailable
 gregtech.gui.fluid_lock.tooltip.enabled=Fluid Locking Enabled
 gregtech.gui.fluid_lock.tooltip.disabled=Fluid Locking Disabled
 gregtech.gui.fluid_voiding.tooltip.enabled=Excess Fluid Voiding Enabled
@@ -5581,7 +5582,7 @@ gregtech.multiblock.pattern.error.batteries=§cMust have at least one non-empty
 gregtech.multiblock.pattern.error.filters=§cAll filters must be the same§r
 gregtech.multiblock.pattern.clear_amount_1=§6Must have a clear 1x1x1 space in front§r
 gregtech.multiblock.pattern.clear_amount_3=§6Must have a clear 3x3x1 space in front§r
-gregtech.multiblock.pattern.single=§6Only this block can be used§r
+gregtech.multiblock.pattern.single=§6Only this block type can be used§r
 gregtech.multiblock.pattern.location_end=§cVery End§r
 gregtech.multiblock.pattern.replaceable_air=Replaceable by Air
 
diff --git a/src/main/resources/assets/gregtech/lang/ja_jp.lang b/src/main/resources/assets/gregtech/lang/ja_jp.lang
index 28cb715332c..648aa40ec41 100644
--- a/src/main/resources/assets/gregtech/lang/ja_jp.lang
+++ b/src/main/resources/assets/gregtech/lang/ja_jp.lang
@@ -2723,11 +2723,6 @@ gregtech.machine.crate.titanium.name=チタン製クレート
 gregtech.machine.crate.tungstensteel.name=タングステンスチール製クレート
 gregtech.crate.tooltip.taped_movement=テープを使うと撤去時に中身を保持できる
 
-# Safe
-gregtech.machine.locked_safe.name=金庫
-gregtech.machine.locked_safe.malfunctioning=§c故障中!
-gregtech.machine.locked_safe.requirements=§7要交換部品:
-
 # Workbench
 gregtech.machine.workbench.name=クラフティングステーション
 gregtech.machine.workbench.tooltip1=Forestryのワークベンチより優秀
diff --git a/src/main/resources/assets/gregtech/lang/ru_ru.lang b/src/main/resources/assets/gregtech/lang/ru_ru.lang
index 349a33f2169..6150191749e 100644
--- a/src/main/resources/assets/gregtech/lang/ru_ru.lang
+++ b/src/main/resources/assets/gregtech/lang/ru_ru.lang
@@ -2454,11 +2454,6 @@ gregtech.machine.crate.stainless_steel.name=Ящик из нержавеющей
 gregtech.machine.crate.titanium.name=Титановый ящик
 gregtech.machine.crate.tungstensteel.name=Ящик из вольфрамовой стали
 
-# Safe
-gregtech.machine.locked_safe.name=Закрытый сейф
-gregtech.machine.locked_safe.malfunctioning=§cСломан!
-gregtech.machine.locked_safe.requirements=§7Требуется замена:
-
 # Workbench
 gregtech.machine.workbench.name=Инженерный верстак
 gregtech.machine.workbench.tooltip1=Лучше, чем в Forestry
diff --git a/src/main/resources/assets/gregtech/lang/zh_cn.lang b/src/main/resources/assets/gregtech/lang/zh_cn.lang
index f410418bbce..88906474bcf 100644
--- a/src/main/resources/assets/gregtech/lang/zh_cn.lang
+++ b/src/main/resources/assets/gregtech/lang/zh_cn.lang
@@ -2718,11 +2718,6 @@ gregtech.machine.crate.titanium.name=钛板条箱
 gregtech.machine.crate.tungstensteel.name=钨钢板条箱
 gregtech.crate.tooltip.taped_movement=可以用胶带打包库存,在破坏时一起带走
 
-# Safe
-gregtech.machine.locked_safe.name=上锁的保险柜
-gregtech.machine.locked_safe.malfunctioning=§c运行故障!
-gregtech.machine.locked_safe.requirements=§7替换部件需求:
-
 # Workbench
 gregtech.machine.workbench.name=合成站
 gregtech.machine.workbench.tooltip1=比林业的好
diff --git a/src/main/resources/assets/gregtech/loot_tables/chests/abandoned_safe_1.json b/src/main/resources/assets/gregtech/loot_tables/chests/abandoned_safe_1.json
deleted file mode 100644
index 7accc9b7c68..00000000000
--- a/src/main/resources/assets/gregtech/loot_tables/chests/abandoned_safe_1.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
-  "pools": [
-    {
-      "name": "resources_pool",
-      "rolls": {
-        "min": 7,
-        "max": 12
-      },
-      "entries": [
-        {
-          "type": "gregtech:ore_dict",
-          "name": "ingotSteel",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 4,
-                "max": 6
-              }
-            }
-          ],
-          "weight": 10
-        },
-        {
-          "type": "gregtech:ore_dict",
-          "name": "plateSteel",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 4,
-                "max": 6
-              }
-            }
-          ],
-          "weight": 10
-        },
-        {
-          "type": "gregtech:ore_dict",
-          "name": "ingotBronze",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 3,
-                "max": 8
-              }
-            }
-          ],
-          "weight": 10
-        },
-        {
-          "type": "gregtech:ore_dict",
-          "name": "ingotAluminium",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 3,
-                "max": 9
-              }
-            }
-          ],
-          "weight": 5
-        },
-        {
-          "type": "gregtech:ore_dict",
-          "name": "plateAluminium",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 3,
-                "max": 9
-              }
-            }
-          ],
-          "weight": 5
-        },
-        {
-          "type": "item",
-          "name": "minecraft:redstone",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 7,
-                "max": 11
-              }
-            }
-          ],
-          "weight": 5
-        },
-        {
-          "type": "item",
-          "name": "minecraft:gold_ingot",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 2,
-                "max": 5
-              }
-            }
-          ],
-          "weight": 5
-        },
-        {
-          "type": "item",
-          "name": "minecraft:diamond",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 1,
-                "max": 2
-              }
-            }
-          ],
-          "weight": 2
-        }
-      ]
-    },
-    {
-      "name": "components_pool",
-      "rolls": 3,
-      "entries": [
-        {
-          "type": "gregtech:meta_item",
-          "name": "electric.motor.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 2,
-                "max": 3
-              }
-            }
-          ],
-          "weight": 20
-        },
-        {
-          "type": "gregtech:meta_item",
-          "name": "conveyor.module.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 1,
-                "max": 2
-              }
-            }
-          ],
-          "weight": 15
-        },
-        {
-          "type": "gregtech:meta_item",
-          "name": "electric.pump.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 1,
-                "max": 2
-              }
-            }
-          ],
-          "weight": 12
-        },
-        {
-          "type": "gregtech:meta_item",
-          "name": "robot.arm.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": 1
-            }
-          ],
-          "weight": 11
-        },
-        {
-          "type": "gregtech:meta_item",
-          "name": "emitter.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 1,
-                "max": 2
-              }
-            }
-          ],
-          "weight": 9
-        },
-        {
-          "type": "gregtech:meta_item",
-          "name": "sensor.lv",
-          "functions": [
-            {
-              "function": "set_count",
-              "count": {
-                "min": 1,
-                "max": 2
-              }
-            }
-          ],
-          "weight": 9
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_bottom.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_bottom.png
deleted file mode 100644
index d02d5b81f6f..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_bottom.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_front.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_front.png
deleted file mode 100644
index 75a871951b1..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_front.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_side.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_side.png
deleted file mode 100644
index ac0793f747b..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_side.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_top.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_top.png
deleted file mode 100644
index e9361efca6f..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/base_top.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_back.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_back.png
deleted file mode 100644
index c12c82dda04..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_back.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_front.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_front.png
deleted file mode 100644
index 7470109b0a3..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_front.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_side.png b/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_side.png
deleted file mode 100644
index 14dffe2b6cb..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/blocks/storage/safe/door_side.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png b/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png
new file mode 100644
index 00000000000..f77849e8f7b
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/progress_bar/progress_bar_unlock.png b/src/main/resources/assets/gregtech/textures/gui/progress_bar/progress_bar_unlock.png
deleted file mode 100644
index 25b60223437..00000000000
Binary files a/src/main/resources/assets/gregtech/textures/gui/progress_bar/progress_bar_unlock.png and /dev/null differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button.png b/src/main/resources/assets/gregtech/textures/gui/widget/button.png
new file mode 100644
index 00000000000..9f9c76a301f
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_auto_collapse_overlay.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_auto_collapse_overlay.png
new file mode 100644
index 00000000000..70a6316ec79
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_auto_collapse_overlay.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_x_overlay.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_x_overlay.png
new file mode 100644
index 00000000000..9046977fd32
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_x_overlay.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png
new file mode 100644
index 00000000000..6615e156d4f
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png
new file mode 100644
index 00000000000..b86b782c8de
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png differ
diff --git a/src/test/java/gregtech/api/util/OreGlobTest.java b/src/test/java/gregtech/api/util/OreGlobTest.java
index f9f888f02f2..fb094484f73 100644
--- a/src/test/java/gregtech/api/util/OreGlobTest.java
+++ b/src/test/java/gregtech/api/util/OreGlobTest.java
@@ -190,11 +190,7 @@ public void matchTest() {
         assertMatch(expr, "anyDoubleSomething", false);
         assertMatch(expr, "shouldn't match!", false);
 
-        expr = compile("$c caseSensitiveMatch");
-        assertMatch(expr, "casesensitivematch", false);
-        assertMatch(expr, "caseSensitiveMatch", true);
-
-        expr = compile("$\\c caseSensitiveMatch");
+        expr = compile("caseSensitiveMatch", true);
         assertMatch(expr, "casesensitivematch", false);
         assertMatch(expr, "caseSensitiveMatch", true);
 
@@ -249,8 +245,6 @@ public void matchTest() {
     @Test
     public void errorTest() {
         assertReport("End of file after escape character ('\\'): \\", true);
-        assertReport("$asdf Tags at middle of expression $12345", true);
-        assertReport("End of file after escape character ('\\'): $123\\", true);
         assertReport(")", true);
         assertReport("a | b | c | ", true);
         assertReport(")))))))", true);
@@ -262,12 +256,15 @@ public void errorTest() {
 
         assertReport("dust !impure !iron", false);
         assertReport("dust !(impure) !(iron)", false);
-        assertReport("$cc 1", false);
     }
 
     private static OreGlob compile(String expression) {
+        return compile(expression, false);
+    }
+
+    private static OreGlob compile(String expression, boolean caseSensitive) {
         long t = System.nanoTime();
-        OreGlobCompileResult result = new OreGlobParser(expression).compile();
+        OreGlobCompileResult result = new OreGlobParser(expression, !caseSensitive).compile();
         assertThat(result.hasError(), is(false));
 
         if (LOG) {
@@ -333,7 +330,7 @@ protected boolean matchesSafely(OreGlob item) {
     }
 
     private static void assertReport(String expression, boolean error) {
-        OreGlobCompileResult result = new OreGlobParser(expression).compile();
+        OreGlobCompileResult result = new OreGlobParser(expression, true).compile();
         assertThat(result, new TypeSafeMatcher<>(OreGlobCompileResult.class) {
 
             @Override