package gregtech.api.recipe; import static gregtech.api.enums.Mods.GregTech; import java.awt.Rectangle; import java.util.Collections; import java.util.Comparator; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.UnaryOperator; import javax.annotation.ParametersAreNonnullByDefault; import com.gtnewhorizons.modularui.api.drawable.FallbackableUITexture; import com.gtnewhorizons.modularui.api.drawable.IDrawable; import com.gtnewhorizons.modularui.api.drawable.UITexture; import com.gtnewhorizons.modularui.api.math.Pos2d; import com.gtnewhorizons.modularui.api.math.Size; import com.gtnewhorizons.modularui.common.widget.ProgressBar; import codechicken.nei.recipe.HandlerInfo; import gregtech.api.gui.modularui.FallbackableSteamTexture; import gregtech.api.gui.modularui.GT_UITextures; import gregtech.api.gui.modularui.SteamTexture; import gregtech.api.objects.overclockdescriber.OverclockDescriber; import gregtech.api.util.GT_Recipe; import gregtech.api.util.GT_RecipeBuilder; import gregtech.api.util.MethodsReturnNonnullByDefault; import gregtech.nei.formatter.INEISpecialInfoFormatter; // spotless:off spotless likes formatting @code to @code /** * Builder class for constructing {@link RecipeMap}. Instantiate this class and call {@link #build} * to retrieve RecipeMap. Smallest example: * *
 * {@code
 *     RecipeMap exampleRecipes = RecipeMapBuilder.of("example")
 *         .maxIO(9, 4, 1, 1)
 *         .build();
 * }
 * 
* * Note that {@link #maxIO} is required to build. */ // spotless:on @SuppressWarnings("unused") @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public final class RecipeMapBuilder { private final String unlocalizedName; private final RecipeMapBackendPropertiesBuilder backendPropertiesBuilder = RecipeMapBackendProperties.builder(); private final RecipeMapBackend.BackendCreator backendCreator; private final BasicUIPropertiesBuilder uiPropertiesBuilder; private final NEIRecipePropertiesBuilder neiPropertiesBuilder = NEIRecipeProperties.builder(); private RecipeMapFrontend.FrontendCreator frontendCreator = RecipeMapFrontend::new; /** * Constructs builder object for {@link RecipeMap} with given backend logic. For custom frontend, * call {@link #frontend} for the created builder object. * * @param unlocalizedName Unique identifier for the recipemap. This is also used as translation key * for NEI recipe GUI header, so add localization for it if needed. * @return New builder object. */ public static RecipeMapBuilder of(String unlocalizedName, RecipeMapBackend.BackendCreator backendCreator) { return new RecipeMapBuilder<>(unlocalizedName, backendCreator); } /** * Constructs builder object for {@link RecipeMap}. * * @param unlocalizedName Unique identifier for the recipemap. This is also used as translation key * for NEI recipe GUI header, so add localization for it if needed. * @return New builder object. */ public static RecipeMapBuilder of(String unlocalizedName) { return new RecipeMapBuilder<>(unlocalizedName, RecipeMapBackend::new); } private RecipeMapBuilder(String unlocalizedName, RecipeMapBackend.BackendCreator backendCreator) { this.unlocalizedName = unlocalizedName; this.backendCreator = backendCreator; this.uiPropertiesBuilder = BasicUIProperties.builder() .progressBarTexture(GT_UITextures.fallbackableProgressbar(unlocalizedName, GT_UITextures.PROGRESSBAR_ARROW)) .neiTransferRectId(unlocalizedName); } // region backend /** * Sets minimum amount of inputs required for the recipes. */ public RecipeMapBuilder minInputs(int minItemInputs, int minFluidInputs) { backendPropertiesBuilder.minItemInputs(minItemInputs) .minFluidInputs(minFluidInputs); return this; } /** * Whether this recipemap should check for equality of special slot when searching recipe. */ public RecipeMapBuilder specialSlotSensitive() { backendPropertiesBuilder.specialSlotSensitive(); return this; } /** * If recipe builder should stop optimizing inputs. */ public RecipeMapBuilder disableOptimize() { backendPropertiesBuilder.disableOptimize(); return this; } /** * Changes how recipes are emitted by a particular recipe builder. Can emit multiple recipe per builder. */ public RecipeMapBuilder recipeEmitter( Function> recipeEmitter) { backendPropertiesBuilder.recipeEmitter(recipeEmitter); return this; } /** * Changes how recipes are emitted by a particular recipe builder. Should not return null. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. */ public RecipeMapBuilder recipeEmitterSingle( Function recipeEmitter) { return recipeEmitter(recipeEmitter.andThen(Collections::singletonList)); } /** * Changes how recipes are emitted by a particular recipe builder. Can emit multiple recipe per builder. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. *

* Unlike {@link #recipeEmitter(Function)}, this one does not clear the existing recipe being emitted, if any */ public RecipeMapBuilder combineRecipeEmitter( Function> recipeEmitter) { backendPropertiesBuilder.combineRecipeEmitter(recipeEmitter); return this; } /** * Changes how recipes are emitted by a particular recipe builder. Effectively add a new recipe per recipe added. * func must not return null. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. *

* Unlike {@link #recipeEmitter(Function)}, this one does not clear the existing recipe being emitted, if any */ public RecipeMapBuilder combineRecipeEmitterSingle( Function recipeEmitter) { return combineRecipeEmitter(recipeEmitter.andThen(Collections::singletonList)); } /** * Runs a custom hook on all recipes added via builder. For more complicated behavior, * use {@link #recipeEmitter}. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. */ public RecipeMapBuilder recipeTransformer(Function recipeTransformer) { backendPropertiesBuilder.recipeTransformer(recipeTransformer); return this; } /** * Runs a custom hook on all recipes added via builder. For more complicated behavior, * use {@link #recipeEmitter}. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. */ public RecipeMapBuilder recipeTransformer(Consumer recipeTransformer) { return recipeTransformer(withIdentityReturn(recipeTransformer)); } /** * Runs a custom hook on all recipes added via builder. For more complicated behavior, * use {@link #recipeEmitter}. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. *

* Unlike {@link #recipeTransformer(Function)}, this one will not replace the existing special handler. * The supplied function will be given the output of existing handler when a recipe is added. */ public RecipeMapBuilder chainRecipeTransformer( Function recipeTransformer) { backendPropertiesBuilder.chainRecipeTransformer(recipeTransformer); return this; } /** * Runs a custom hook on all recipes added via builder. For more complicated behavior, * use {@link #recipeEmitter}. *

* Recipes added via one of the overloads of addRecipe will NOT be affected by this function. *

* Unlike {@link #recipeTransformer(Function)}, this one will not replace the existing special handler. * The supplied function will be given the output of existing handler when a recipe is added. */ public RecipeMapBuilder chainRecipeTransformer(Consumer recipeTransformer) { return chainRecipeTransformer(withIdentityReturn(recipeTransformer)); } // endregion // region frontend UI properties /** * Sets how many item/fluid inputs/outputs does this recipemap usually has at most. * It does not actually restrict the number of items that can be used in recipes. */ public RecipeMapBuilder maxIO(int maxItemInputs, int maxItemOutputs, int maxFluidInputs, int maxFluidOutputs) { uiPropertiesBuilder.maxItemInputs(maxItemInputs) .maxItemOutputs(maxItemOutputs) .maxFluidInputs(maxFluidInputs) .maxFluidOutputs(maxFluidOutputs); return this; } /** * Sets function to get overlay for slots. */ public RecipeMapBuilder slotOverlays(BasicUIProperties.SlotOverlayGetter slotOverlays) { uiPropertiesBuilder.slotOverlays(slotOverlays); return this; } /** * Sets function to get overlay for slots of steam machines. */ public RecipeMapBuilder slotOverlaysSteam(BasicUIProperties.SlotOverlayGetter slotOverlaysSteam) { uiPropertiesBuilder.slotOverlaysSteam(slotOverlaysSteam); return this; } /** * Sets texture and animation direction of the progressbar. *

* Unless specified, size should be (20, 36), consisting of two parts; * First is (20, 18) size of "empty" image at the top, Second is (20, 18) size of "filled" image at the bottom. *

* By default, it's set to {@code GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT}. */ public RecipeMapBuilder progressBar(UITexture texture, ProgressBar.Direction direction) { return progressBarWithFallback(GT_UITextures.fallbackableProgressbar(unlocalizedName, texture), direction); } /** * Sets progressbar texture with right direction. *

* Unless specified, size should be (20, 36), consisting of two parts; * First is (20, 18) size of "empty" image at the top, Second is (20, 18) size of "filled" image at the bottom. */ public RecipeMapBuilder progressBar(UITexture texture) { return progressBar(texture, ProgressBar.Direction.RIGHT); } /** * Some resource packs want to use custom progress bar textures even for plain arrow. This method allows them to * add unique textures, yet other packs don't need to make textures for every recipemap. */ private RecipeMapBuilder progressBarWithFallback(FallbackableUITexture texture, ProgressBar.Direction direction) { uiPropertiesBuilder.progressBarTexture(texture) .progressBarDirection(direction); return this; } /** * Sets progressbar texture for steam machines. *

* Unless specified, size should be (20, 36), consisting of two parts; * First is (20, 18) size of "empty" image at the top, Second is (20, 18) size of "filled" image at the bottom. */ public RecipeMapBuilder progressBarSteam(SteamTexture texture) { return progressBarSteamWithFallback( new FallbackableSteamTexture( SteamTexture.fullImage(GregTech.ID, "gui/progressbar/" + unlocalizedName + "_%s"), texture)); } private RecipeMapBuilder progressBarSteamWithFallback(FallbackableSteamTexture texture) { uiPropertiesBuilder.progressBarTextureSteam(texture); return this; } /** * Sets size of the progressbar. (20, 36) by default. */ public RecipeMapBuilder progressBarSize(int x, int y) { uiPropertiesBuilder.progressBarSize(new Size(x, y)); return this; } /** * Sets position of the progressbar. (78, 24) by default. */ public RecipeMapBuilder progressBarPos(int x, int y) { uiPropertiesBuilder.progressBarPos(new Pos2d(x, y)); return this; } /** * Stops adding progressbar to the UI. */ public RecipeMapBuilder dontUseProgressBar() { uiPropertiesBuilder.useProgressBar(false); return this; } /** * Configures this recipemap to use special slot. This means special slot shows up on NEI and tooltip for * special slot on basic machine GUI indicates it has actual usage. */ public RecipeMapBuilder useSpecialSlot() { uiPropertiesBuilder.useSpecialSlot(true); return this; } /** * Adds GUI area where clicking shows up all the recipes available. * * @see codechicken.nei.recipe.TemplateRecipeHandler.RecipeTransferRect */ public RecipeMapBuilder neiTransferRect(int x, int y, int width, int height) { uiPropertiesBuilder.addNEITransferRect(new Rectangle(x, y, width, height)); return this; } /** * Sets ID used to open NEI recipe GUI when progressbar is clicked. */ public RecipeMapBuilder neiTransferRectId(String neiTransferRectId) { uiPropertiesBuilder.neiTransferRectId(neiTransferRectId); return this; } /** * Adds additional textures shown on GUI. */ public RecipeMapBuilder addSpecialTexture(int x, int y, int width, int height, IDrawable texture) { uiPropertiesBuilder.addSpecialTexture(new Size(width, height), new Pos2d(x, y), texture); return this; } /** * Adds additional textures shown on steam machine GUI. */ public RecipeMapBuilder addSpecialTextureSteam(int x, int y, int width, int height, SteamTexture texture) { uiPropertiesBuilder.addSpecialTextureSteam(new Size(width, height), new Pos2d(x, y), texture); return this; } /** * Sets logo shown on GUI. GregTech logo by default. */ public RecipeMapBuilder logo(IDrawable logo) { uiPropertiesBuilder.logo(logo); return this; } /** * Sets size of logo. (17, 17) by default. */ public RecipeMapBuilder logoSize(int width, int height) { uiPropertiesBuilder.logoSize(new Size(width, height)); return this; } /** * Sets position of logo. (152, 63) by default. */ public RecipeMapBuilder logoPos(int x, int y) { uiPropertiesBuilder.logoPos(new Pos2d(x, y)); return this; } /** * Sets amperage for the recipemap. */ public RecipeMapBuilder amperage(int amperage) { uiPropertiesBuilder.amperage(amperage); return this; } // endregion // region frontend NEI properties /** * Stops adding dedicated NEI recipe page for this recipemap. This does not prevent adding transferrect * for the machine GUI. */ public RecipeMapBuilder disableRegisterNEI() { neiPropertiesBuilder.disableRegisterNEI(); return this; } /** * Sets properties of NEI handler info this recipemap belongs to. You can specify icon shown on recipe tab, * handler height, number of recipes per page, etc. Either use supplied template or return newly constructed one. *

* Invocation of the builder creator is delayed until the actual registration (FMLLoadCompleteEvent), * so you can safely use itemstack that doesn't exist as of recipemap initialization. *

* If this method is not used, handler icon will be inferred from recipe catalysts associated with this recipemap. *

* Precisely, what's registered to NEI is {@link RecipeCategory}, not RecipeMap. However, handler info supplied * by this method will be used for default category where most of the recipes belong to. */ public RecipeMapBuilder neiHandlerInfo(UnaryOperator handlerInfoCreator) { neiPropertiesBuilder.handlerInfoCreator(handlerInfoCreator); return this; } /** * Sets offset of background shown on NEI. */ public RecipeMapBuilder neiRecipeBackgroundSize(int width, int height) { neiPropertiesBuilder.recipeBackgroundSize(new Size(width, height)); return this; } /** * Sets size of background shown on NEI. */ public RecipeMapBuilder neiRecipeBackgroundOffset(int x, int y) { neiPropertiesBuilder.recipeBackgroundOffset(new Pos2d(x, y)); return this; } /** * Sets formatter for special description for the recipe, mainly {@link gregtech.api.util.GT_Recipe#mSpecialValue}. */ public RecipeMapBuilder neiSpecialInfoFormatter(INEISpecialInfoFormatter neiSpecialInfoFormatter) { neiPropertiesBuilder.neiSpecialInfoFormatter(neiSpecialInfoFormatter); return this; } /** * Sets whether to show oredict equivalent item outputs on NEI. */ public RecipeMapBuilder unificateOutputNEI(boolean unificateOutputNEI) { neiPropertiesBuilder.unificateOutput(unificateOutputNEI); return this; } /** * Sets NEI recipe handler to use a custom filter method {@link OverclockDescriber#canHandle} to limit the shown * recipes when searching recipes with recipe catalyst. Without calling this method, the voltage of the recipe is * the only factor to filter recipes by default. *

* This method on its own doesn't do anything. You need to bind custom {@link OverclockDescriber} object to machines * that will be shown as recipe catalysts for this recipemap by implementing * {@link gregtech.api.interfaces.tileentity.IOverclockDescriptionProvider}. */ public RecipeMapBuilder useCustomFilterForNEI() { neiPropertiesBuilder.useCustomFilter(); return this; } /** * Stops rendering the actual stack size of items on NEI. */ public RecipeMapBuilder disableRenderRealStackSizes() { neiPropertiesBuilder.disableRenderRealStackSizes(); return this; } /** * Sets custom comparator for NEI recipe sort. */ public RecipeMapBuilder neiRecipeComparator(Comparator comparator) { neiPropertiesBuilder.recipeComparator(comparator); return this; } // endregion /** * Sets custom frontend logic. For custom backend, pass it to {@link #of(String, RecipeMapBackend.BackendCreator)}. */ public RecipeMapBuilder frontend(RecipeMapFrontend.FrontendCreator frontendCreator) { this.frontendCreator = frontendCreator; return this; } /** * Builds new recipemap. * * @return Recipemap object with backend type parameter, which is {@code RecipeMapFrontend} unless specified. */ public RecipeMap build() { return new RecipeMap<>( unlocalizedName, backendCreator.create(backendPropertiesBuilder), frontendCreator.create(uiPropertiesBuilder, neiPropertiesBuilder)); } private static Function withIdentityReturn(Consumer func) { return r -> { func.accept(r); return r; }; } }