aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/nei
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/nei')
-rw-r--r--src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java801
-rw-r--r--src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java54
-rw-r--r--src/main/java/gregtech/nei/NEI_GT_Config.java183
-rw-r--r--src/main/java/gregtech/nei/RecipeDisplayInfo.java99
-rw-r--r--src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java10
-rw-r--r--src/main/java/gregtech/nei/dumper/GregTechIDDumper.java61
-rw-r--r--src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java10
-rw-r--r--src/main/java/gregtech/nei/dumper/MaterialDumper.java39
-rw-r--r--src/main/java/gregtech/nei/dumper/MetaItemDumper.java60
-rw-r--r--src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java37
-rw-r--r--src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java46
-rw-r--r--src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java10
-rw-r--r--src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java10
-rw-r--r--src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java36
-rw-r--r--src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java27
-rw-r--r--src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java59
-rw-r--r--src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java30
-rw-r--r--src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java24
-rw-r--r--src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java49
19 files changed, 1645 insertions, 0 deletions
diff --git a/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java b/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java
new file mode 100644
index 0000000000..99e7dc5459
--- /dev/null
+++ b/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java
@@ -0,0 +1,801 @@
+package gregtech.nei;
+
+import static gregtech.api.enums.GT_Values.V;
+
+import java.awt.Rectangle;
+import java.lang.ref.SoftReference;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.apache.commons.lang3.Range;
+import org.lwjgl.opengl.GL11;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.UIInfos;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+
+import codechicken.nei.NEIClientUtils;
+import codechicken.nei.PositionedStack;
+import codechicken.nei.recipe.GuiRecipe;
+import codechicken.nei.recipe.ICraftingHandler;
+import codechicken.nei.recipe.IUsageHandler;
+import codechicken.nei.recipe.RecipeCatalysts;
+import codechicken.nei.recipe.TemplateRecipeHandler;
+import gregtech.GT_Mod;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SteamVariant;
+import gregtech.api.gui.GT_GUIColorOverride;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IOverclockDescriptionProvider;
+import gregtech.api.objects.ItemData;
+import gregtech.api.objects.overclockdescriber.EUNoOverclockDescriber;
+import gregtech.api.objects.overclockdescriber.OverclockDescriber;
+import gregtech.api.recipe.BasicUIProperties;
+import gregtech.api.recipe.NEIRecipeProperties;
+import gregtech.api.recipe.RecipeCategory;
+import gregtech.api.recipe.RecipeCategorySetting;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.RecipeMapFrontend;
+import gregtech.api.util.GT_OreDictUnificator;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.blocks.GT_Item_Machines;
+import gregtech.common.gui.modularui.UIHelper;
+
+public class GT_NEI_DefaultHandler extends TemplateRecipeHandler {
+
+ private static final int offsetX = 5;
+ private static final int offsetY = 11;
+ protected static final Pos2d WINDOW_OFFSET = new Pos2d(-offsetX, -offsetY);
+
+ private static final ConcurrentMap<RecipeCategory, SortedRecipeListCache> CACHE = new ConcurrentHashMap<>();
+
+ private static final int RECIPE_NAME_WIDTH = 140;
+
+ /**
+ * Static version of {@link TemplateRecipeHandler#cycleticks}. Can be referenced from cached recipes.
+ */
+ private static int cycleTicksStatic = Math.abs((int) System.currentTimeMillis());
+ /**
+ * Basically {@link #cycleTicksStatic} but always updated even while holding shift
+ */
+ private static int drawTicks;
+ private static final int PROGRESSBAR_CYCLE_TICKS = 200;
+
+ protected final RecipeCategory recipeCategory;
+ protected final RecipeMap<?> recipeMap;
+ protected final RecipeMapFrontend frontend;
+ protected final BasicUIProperties uiProperties;
+ protected final NEIRecipeProperties neiProperties;
+
+ protected final ModularWindow modularWindow;
+ protected final ItemStackHandler itemInputsInventory;
+ protected final ItemStackHandler itemOutputsInventory;
+ protected final ItemStackHandler specialSlotInventory;
+ protected final ItemStackHandler fluidInputsInventory;
+ protected final ItemStackHandler fluidOutputsInventory;
+
+ protected OverclockDescriber overclockDescriber;
+ /**
+ * Localized name of this handler displayed on the top.
+ */
+ private String recipeNameDisplay;
+ /**
+ * Tooltip shown while hovering over header of this handler. Can be null if the full name fits in the screen.
+ */
+ private NEIHandlerAbsoluteTooltip recipeNameTooltip;
+
+ protected final GT_GUIColorOverride colorOverride = GT_GUIColorOverride
+ .get(GT_UITextures.BACKGROUND_NEI_SINGLE_RECIPE.location);
+ private int neiTextColorOverride = -1;
+
+ public GT_NEI_DefaultHandler(RecipeCategory recipeCategory) {
+ this.recipeCategory = recipeCategory;
+ this.recipeMap = recipeCategory.recipeMap;
+ this.frontend = recipeMap.getFrontend();
+ this.uiProperties = frontend.getUIProperties();
+ this.neiProperties = frontend.getNEIProperties();
+ uiProperties.neiTransferRect.forEach(transferRect -> {
+ transferRect = new Rectangle(transferRect);
+ transferRect.translate(WINDOW_OFFSET.x, WINDOW_OFFSET.y);
+ this.transferRects.add(new RecipeTransferRect(transferRect, recipeMap.unlocalizedName));
+ });
+
+ ModularWindow.Builder builder = frontend.createNEITemplate(
+ itemInputsInventory = new ItemStackHandler(uiProperties.maxItemInputs),
+ itemOutputsInventory = new ItemStackHandler(uiProperties.maxItemOutputs),
+ specialSlotInventory = new ItemStackHandler(1),
+ fluidInputsInventory = new ItemStackHandler(uiProperties.maxFluidInputs),
+ fluidOutputsInventory = new ItemStackHandler(uiProperties.maxFluidOutputs),
+ () -> ((float) getDrawTicks() % PROGRESSBAR_CYCLE_TICKS) / PROGRESSBAR_CYCLE_TICKS,
+ WINDOW_OFFSET);
+ modularWindow = builder.build();
+ UIInfos.initializeWindow(Minecraft.getMinecraft().thePlayer, modularWindow);
+ }
+
+ public RecipeMap<?> getRecipeMap() {
+ return recipeMap;
+ }
+
+ private SortedRecipeListCache getCacheHolder() {
+ return CACHE.computeIfAbsent(recipeCategory, m -> new SortedRecipeListCache());
+ }
+
+ public List<CachedDefaultRecipe> getCache() {
+ SortedRecipeListCache cacheHolder = getCacheHolder();
+ List<CachedDefaultRecipe> cache;
+ if (cacheHolder.getCachedRecipesVersion() != GT_Mod.gregtechproxy.getNEIReloadCount()
+ || (cache = cacheHolder.getCachedRecipes()) == null) {
+ try {
+ RecipeCategory defaultCategory = recipeMap.getDefaultRecipeCategory();
+ Collection<GT_Recipe> recipes;
+ if (this.recipeCategory == defaultCategory) {
+ // This is main category, so merge categories that are configured as such
+ Stream<GT_Recipe> recipesToMerge = recipeMap.getBackend()
+ .getRecipeCategoryMap()
+ .entrySet()
+ .stream()
+ .flatMap(entry -> {
+ boolean merge = entry.getKey() != defaultCategory
+ && GT_Mod.gregtechproxy.recipeCategorySettings
+ .getOrDefault(entry.getKey(), RecipeCategorySetting.getDefault())
+ == RecipeCategorySetting.MERGE;
+ return merge ? entry.getValue()
+ .stream() : Stream.empty();
+ });
+ recipes = Stream.concat(
+ recipesToMerge,
+ recipeMap.getBackend()
+ .getRecipesByCategory(defaultCategory)
+ .stream())
+ .collect(Collectors.toList());
+ } else {
+ // This is "sub" category
+ if (GT_Mod.gregtechproxy.recipeCategorySettings
+ .getOrDefault(recipeCategory, RecipeCategorySetting.getDefault())
+ == RecipeCategorySetting.ENABLE) {
+ recipes = recipeMap.getBackend()
+ .getRecipesByCategory(recipeCategory);
+ } else {
+ recipes = Collections.emptyList();
+ }
+ }
+ cache = recipes.stream() // do not use parallel stream. This is already parallelized by NEI
+ .filter(r -> !r.mHidden)
+ .sorted(neiProperties.comparator)
+ .map(CachedDefaultRecipe::new)
+ .collect(Collectors.toList());
+ // while the NEI parallelize handlers, for each individual handler it still uses sequential execution
+ // model,
+ // so we do not need any synchronization here
+ // even if it does break, at worst case it's just recreating the cache multiple times, which should be
+ // fine
+ cacheHolder.setCachedRecipes(cache);
+ cacheHolder.setCachedRecipesVersion(GT_Mod.gregtechproxy.getNEIReloadCount());
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Could not construct GT NEI Handler cache for category " + recipeCategory
+ + ", display name "
+ + recipeNameDisplay,
+ e);
+ }
+ }
+ return cache;
+ }
+
+ @Override
+ public TemplateRecipeHandler newInstance() {
+ return new GT_NEI_DefaultHandler(recipeCategory);
+ }
+
+ @Override
+ public void loadCraftingRecipes(String outputId, Object... results) {
+ if (outputId.equals(recipeMap.unlocalizedName)) {
+ if (results.length > 0 && results[0] instanceof OverclockDescriber) {
+ overclockDescriber = (OverclockDescriber) results[0];
+ if (neiProperties.useCustomFilter) {
+ loadTieredRecipesWithCustomFilter(overclockDescriber);
+ } else {
+ loadTieredRecipesUpTo(overclockDescriber.getTier());
+ }
+ } else {
+ arecipes.addAll(getCache());
+ }
+ } else {
+ super.loadCraftingRecipes(outputId, results);
+ }
+ }
+
+ @Override
+ public void loadCraftingRecipes(ItemStack aResult) {
+ ItemData tPrefixMaterial = GT_OreDictUnificator.getAssociation(aResult);
+
+ ArrayList<ItemStack> tResults = new ArrayList<>();
+ tResults.add(aResult);
+ tResults.add(GT_OreDictUnificator.get(true, aResult));
+ if ((tPrefixMaterial != null) && (!tPrefixMaterial.mBlackListed)
+ && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) {
+ for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) {
+ tResults.add(GT_OreDictUnificator.get(tPrefix, tPrefixMaterial.mMaterial.mMaterial, 1L));
+ }
+ }
+ if (aResult.getUnlocalizedName()
+ .startsWith("gt.blockores")) {
+ for (int i = 0; i < 8; i++) {
+ tResults.add(new ItemStack(aResult.getItem(), 1, aResult.getItemDamage() % 1000 + i * 1000));
+ }
+ }
+ addFluidStacks(aResult, tResults);
+ for (CachedDefaultRecipe recipe : getCache()) {
+ if (tResults.stream()
+ .anyMatch(stack -> recipe.contains(recipe.mOutputs, stack))) arecipes.add(recipe);
+ }
+ }
+
+ private void addFluidStacks(ItemStack aStack, ArrayList<ItemStack> tResults) {
+ FluidStack tFluid = GT_Utility.getFluidForFilledItem(aStack, true);
+ FluidStack tFluidStack;
+ if (tFluid != null) {
+ tFluidStack = tFluid;
+ tResults.add(GT_Utility.getFluidDisplayStack(tFluid, false));
+ } else tFluidStack = GT_Utility.getFluidFromDisplayStack(aStack);
+ if (tFluidStack != null) {
+ tResults.addAll(GT_Utility.getContainersFromFluid(tFluidStack));
+ }
+ }
+
+ private void loadTieredRecipesWithCustomFilter(OverclockDescriber overclockDescriber) {
+ arecipes.addAll(getTieredRecipesWithCustomFilter(overclockDescriber));
+ }
+
+ private List<CachedDefaultRecipe> getTieredRecipesWithCustomFilter(OverclockDescriber overclockDescriber) {
+ List<CachedDefaultRecipe> recipes = getCache();
+ if (!recipes.isEmpty()) {
+ recipes = recipes.stream()
+ .filter(recipe -> overclockDescriber.canHandle(recipe.mRecipe))
+ .collect(Collectors.toList());
+ }
+ return recipes;
+ }
+
+ private void loadTieredRecipesUpTo(byte upperTier) {
+ arecipes.addAll(getTieredRecipes(upperTier));
+ }
+
+ private List<CachedDefaultRecipe> getTieredRecipes(byte upperTier) {
+ List<CachedDefaultRecipe> recipes = getCache();
+ if (!recipes.isEmpty()) {
+ Range<Integer> indexRange = getCacheHolder().getIndexRangeForTiers((byte) 0, upperTier);
+ recipes = recipes.subList(indexRange.getMinimum(), indexRange.getMaximum() + 1);
+ }
+ return recipes;
+ }
+
+ @Override
+ public void loadUsageRecipes(ItemStack aInput) {
+ ItemData tPrefixMaterial = GT_OreDictUnificator.getAssociation(aInput);
+ ArrayList<ItemStack> tInputs = new ArrayList<>();
+ tInputs.add(aInput);
+ tInputs.add(GT_OreDictUnificator.get(false, aInput));
+ if ((tPrefixMaterial != null) && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) {
+ for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) {
+ tInputs.add(GT_OreDictUnificator.get(tPrefix, tPrefixMaterial.mMaterial.mMaterial, 1L));
+ }
+ }
+ addFluidStacks(aInput, tInputs);
+ for (CachedDefaultRecipe recipe : getCache()) {
+ if (tInputs.stream()
+ .anyMatch(stack -> recipe.contains(recipe.mInputs, stack))) arecipes.add(recipe);
+ }
+ }
+
+ @Override
+ public IUsageHandler getUsageAndCatalystHandler(String inputId, Object... ingredients) {
+ if (inputId.equals("item")) {
+ ItemStack candidate = (ItemStack) ingredients[0];
+ GT_NEI_DefaultHandler handler = (GT_NEI_DefaultHandler) newInstance();
+ if (RecipeCatalysts.containsCatalyst(handler, candidate)) {
+ IMetaTileEntity metaTile = GT_Item_Machines.getMetaTileEntity(candidate);
+ OverclockDescriber overclockDescriber;
+ if (metaTile instanceof IOverclockDescriptionProvider provider) {
+ overclockDescriber = provider.getOverclockDescriber();
+ } else {
+ overclockDescriber = null;
+ }
+ handler.loadCraftingRecipes(recipeMap.unlocalizedName, overclockDescriber);
+ return handler;
+ }
+ }
+ return this.getUsageHandler(inputId, ingredients);
+ }
+
+ @Override
+ public ICraftingHandler getRecipeHandler(String outputId, Object... results) {
+ GT_NEI_DefaultHandler handler = (GT_NEI_DefaultHandler) super.getRecipeHandler(outputId, results);
+ if (results.length > 0 && results[0] instanceof OverclockDescriber) {
+ handler.overclockDescriber = (OverclockDescriber) results[0];
+ }
+ return handler;
+ }
+
+ @Override
+ public String getOverlayIdentifier() {
+ return recipeCategory.unlocalizedName;
+ }
+
+ @Override
+ public void drawBackground(int recipe) {
+ drawUI(modularWindow);
+ }
+
+ @Override
+ public void drawForeground(int recipe) {
+ GL11.glColor4f(1, 1, 1, 1);
+ GL11.glDisable(GL11.GL_LIGHTING);
+ drawExtras(recipe);
+ }
+
+ @Override
+ public void onUpdate() {
+ super.onUpdate();
+ if (!NEIClientUtils.shiftKey()) cycleTicksStatic++;
+ drawTicks++;
+ }
+
+ @Override
+ public int recipiesPerPage() {
+ return 1;
+ }
+
+ @Override
+ public String getRecipeName() {
+ if (recipeNameDisplay == null) {
+ recipeNameDisplay = computeRecipeName();
+ neiTextColorOverride = colorOverride.getTextColorOrDefault("nei", -1);
+ }
+ return recipeNameDisplay;
+ }
+
+ private String computeRecipeName() {
+ String recipeName = StatCollector.translateToLocal(recipeCategory.unlocalizedName);
+ if (overclockDescriber != null) {
+ String suffix = "(" + overclockDescriber.getTierString() + ")";
+ // Space will be cropped if title exceeds
+ return shrinkRecipeName(recipeName + " ", suffix);
+ } else {
+ return shrinkRecipeName(recipeName, "");
+ }
+ }
+
+ private String shrinkRecipeName(final String originalRecipeName, final String suffix) {
+ FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
+ int suffixWidth = fontRenderer.getStringWidth(suffix);
+ if (fontRenderer.getStringWidth(originalRecipeName) + suffixWidth <= RECIPE_NAME_WIDTH) {
+ return originalRecipeName + suffix;
+ }
+
+ final String ellipsis = "...";
+ final int ellipsisWidth = fontRenderer.getStringWidth(ellipsis);
+ String recipeName = originalRecipeName;
+ do {
+ recipeName = recipeName.substring(0, recipeName.length() - 1);
+ } while (fontRenderer.getStringWidth(recipeName) + ellipsisWidth + suffixWidth > RECIPE_NAME_WIDTH);
+ setupRecipeNameTooltip(originalRecipeName + suffix);
+ return recipeName + ellipsis + suffix;
+ }
+
+ private void setupRecipeNameTooltip(String tooltip) {
+ recipeNameTooltip = new NEIHandlerAbsoluteTooltip(tooltip, new Rectangle(13, -34, RECIPE_NAME_WIDTH - 1, 11));
+ }
+
+ @Override
+ public String getRecipeTabName() {
+ return StatCollector.translateToLocal(recipeCategory.unlocalizedName);
+ }
+
+ @Override
+ public String getGuiTexture() {
+ // not called
+ return "";
+ }
+
+ @Override
+ public List<String> handleItemTooltip(GuiRecipe<?> gui, ItemStack aStack, List<String> currentTip,
+ int aRecipeIndex) {
+ if (recipeNameTooltip != null) {
+ recipeNameTooltip.handleTooltip(currentTip, aRecipeIndex);
+ }
+ if (aStack == null) {
+ return currentTip;
+ }
+
+ CachedRecipe tObject = this.arecipes.get(aRecipeIndex);
+ if (tObject instanceof CachedDefaultRecipe) {
+ currentTip = frontend.handleNEIItemTooltip(aStack, currentTip, (CachedDefaultRecipe) tObject);
+ }
+ return currentTip;
+ }
+
+ @Override
+ public void drawExtras(int aRecipeIndex) {
+ CachedDefaultRecipe cachedRecipe = ((CachedDefaultRecipe) this.arecipes.get(aRecipeIndex));
+
+ drawDescription(cachedRecipe);
+ frontend.drawNEIOverlays(cachedRecipe);
+ }
+
+ private void drawDescription(CachedDefaultRecipe cachedRecipe) {
+ GT_Recipe recipe = cachedRecipe.mRecipe;
+ if (overclockDescriber == null) {
+ // By default, assume generic LV EU with no overclocks
+ overclockDescriber = new EUNoOverclockDescriber((byte) 1, uiProperties.amperage);
+ }
+
+ GT_OverclockCalculator calculator = overclockDescriber.createCalculator(
+ new GT_OverclockCalculator().setRecipeEUt(recipe.mEUt)
+ .setDuration(recipe.mDuration),
+ recipe);
+ calculator.calculate();
+
+ frontend.drawDescription(
+ new RecipeDisplayInfo(
+ recipe,
+ recipeMap,
+ overclockDescriber,
+ calculator,
+ getDescriptionYOffset(),
+ neiTextColorOverride));
+ }
+
+ protected int getDescriptionYOffset() {
+ return neiProperties.recipeBackgroundSize.height + neiProperties.recipeBackgroundOffset.y + WINDOW_OFFSET.y + 3;
+ }
+
+ protected void drawUI(ModularWindow window) {
+ for (IDrawable background : window.getBackground()) {
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(
+ WINDOW_OFFSET.x + neiProperties.recipeBackgroundOffset.x,
+ WINDOW_OFFSET.y + neiProperties.recipeBackgroundOffset.y,
+ 0);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ background.draw(Pos2d.ZERO, window.getSize(), 0);
+ GlStateManager.popMatrix();
+ }
+
+ for (Widget widget : window.getChildren()) {
+ // NEI already did translation, so we can't use Widget#drawInternal here
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(widget.getPos().x, widget.getPos().y, 0);
+ GlStateManager.color(1, 1, 1, window.getAlpha());
+ GlStateManager.enableBlend();
+
+ // maybe we can use Minecraft#timer but none of the IDrawables use partialTicks
+ widget.drawBackground(0);
+
+ // noinspection OverrideOnly // It's either suppressing this warning or changing ModularUI
+ widget.draw(0);
+ GlStateManager.popMatrix();
+ }
+ }
+
+ public static int getDrawTicks() {
+ return drawTicks;
+ }
+
+ public static class FixedPositionedStack extends PositionedStack {
+
+ public static final DecimalFormat chanceFormat = new DecimalFormat("##0.##%");
+ public final int mChance;
+ public final int realStackSize;
+ public final boolean renderRealStackSize;
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSizes, int x, int y) {
+ this(object, renderRealStackSizes, x, y, 0, true);
+ }
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSizes, int x, int y, boolean aUnificate) {
+ this(object, renderRealStackSizes, x, y, 0, aUnificate);
+ }
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSize, int x, int y, int aChance,
+ boolean aUnificate) {
+ super(aUnificate ? GT_OreDictUnificator.getNonUnifiedStacks(object) : object, x, y, true);
+ this.mChance = aChance;
+ realStackSize = item != null ? item.stackSize : 0;
+ this.renderRealStackSize = renderRealStackSize;
+ if (!renderRealStackSize) {
+ for (ItemStack stack : items) {
+ stack.stackSize = 1;
+ }
+ }
+ }
+
+ public boolean isChanceBased() {
+ return mChance > 0 && mChance < 10000;
+ }
+
+ public String getChanceText() {
+ return chanceFormat.format((float) mChance / 10000);
+ }
+
+ public boolean isNotConsumed() {
+ return !isFluid() && item.stackSize == 0;
+ }
+
+ public boolean isFluid() {
+ return ItemList.Display_Fluid.isStackEqual(item, true, true);
+ }
+ }
+
+ public class CachedDefaultRecipe extends TemplateRecipeHandler.CachedRecipe {
+
+ public final GT_Recipe mRecipe;
+ public final List<PositionedStack> mOutputs;
+ public final List<PositionedStack> mInputs;
+
+ public CachedDefaultRecipe(GT_Recipe aRecipe) {
+ super();
+ this.mRecipe = aRecipe;
+ mOutputs = new ArrayList<>();
+ mInputs = new ArrayList<>();
+
+ for (Widget child : modularWindow.getChildren()) {
+ if (child instanceof SlotWidget widget) {
+ if (widget.getMcSlot()
+ .getItemHandler() == itemInputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ Object input = aRecipe instanceof GT_Recipe.GT_Recipe_WithAlt
+ ? ((GT_Recipe.GT_Recipe_WithAlt) aRecipe).getAltRepresentativeInput(i)
+ : aRecipe.getRepresentativeInput(i);
+ if (input != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ input,
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1,
+ true));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == itemOutputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ ItemStack output = aRecipe.getRepresentativeOutput(i);
+ if (output != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ output,
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1,
+ aRecipe.getOutputChance(i),
+ GT_NEI_DefaultHandler.this.neiProperties.unificateOutput));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == specialSlotInventory) {
+ if (aRecipe.mSpecialItems != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ aRecipe.mSpecialItems,
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+
+ } else if (widget.getMcSlot()
+ .getItemHandler() == fluidInputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ if (aRecipe.mFluidInputs.length > i && aRecipe.mFluidInputs[i] != null
+ && aRecipe.mFluidInputs[i].getFluid() != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ GT_Utility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true),
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == fluidOutputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ if (aRecipe.mFluidOutputs.length > i && aRecipe.mFluidOutputs[i] != null
+ && aRecipe.mFluidOutputs[i].getFluid() != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ GT_Utility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true),
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+ }
+ }
+ }
+
+ // items and fluids that exceed usual count
+ UIHelper.forEachSlots((i, backgrounds, pos) -> {
+ if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxItemInputs && aRecipe.mInputs[i] != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ aRecipe.mInputs[i],
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1,
+ true));
+ }
+ }, (i, backgrounds, pos) -> {
+ if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxItemOutputs && aRecipe.mOutputs[i] != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ aRecipe.mOutputs[i],
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1,
+ aRecipe.getOutputChance(i),
+ GT_NEI_DefaultHandler.this.neiProperties.unificateOutput));
+ }
+ }, (i, backgrounds, pos) -> {}, (i, backgrounds, pos) -> {
+ if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxFluidInputs && aRecipe.mFluidInputs[i] != null
+ && aRecipe.mFluidInputs[i].getFluid() != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ GT_Utility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true),
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1));
+ }
+ }, (i, backgrounds, pos) -> {
+ if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxFluidOutputs && aRecipe.mFluidOutputs[i] != null
+ && aRecipe.mFluidOutputs[i].getFluid() != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ GT_Utility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true),
+ GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1));
+ }
+ },
+ IDrawable.EMPTY,
+ IDrawable.EMPTY,
+ GT_NEI_DefaultHandler.this.frontend.getUIProperties(),
+ aRecipe.mInputs.length,
+ aRecipe.mOutputs.length,
+ aRecipe.mFluidInputs.length,
+ aRecipe.mFluidOutputs.length,
+ SteamVariant.NONE,
+ WINDOW_OFFSET);
+ }
+
+ @Override
+ public List<PositionedStack> getIngredients() {
+ return getCycledIngredients(cycleTicksStatic / 10, this.mInputs);
+ }
+
+ @Override
+ public PositionedStack getResult() {
+ return null;
+ }
+
+ @Override
+ public List<PositionedStack> getOtherStacks() {
+ return this.mOutputs;
+ }
+ }
+
+ private class SortedRecipeListCache {
+
+ private int mCachedRecipesVersion = -1;
+
+ @Nullable
+ private SoftReference<List<CachedDefaultRecipe>> mCachedRecipes;
+
+ private Map<Byte, Range<Integer>> mTierIndexes;
+ private Range<Byte> mTierRange;
+
+ public int getCachedRecipesVersion() {
+ return mCachedRecipesVersion;
+ }
+
+ public void setCachedRecipesVersion(int aCachedRecipesVersion) {
+ this.mCachedRecipesVersion = aCachedRecipesVersion;
+ }
+
+ @Nullable
+ public List<CachedDefaultRecipe> getCachedRecipes() {
+ return mCachedRecipes == null ? null : mCachedRecipes.get();
+ }
+
+ public void setCachedRecipes(@Nonnull List<CachedDefaultRecipe> aCachedRecipes) {
+ this.mCachedRecipes = new SoftReference<>(aCachedRecipes);
+ }
+
+ public Range<Integer> getIndexRangeForTiers(byte lowerTier, byte upperTier) {
+ if (mTierIndexes == null) {
+ computeTierIndexes();
+ }
+ return Range.between(getLowIndexForTier(lowerTier), getHighIndexForTier(upperTier));
+ }
+
+ private void computeTierIndexes() {
+ // Holds 16 elements without rehashing
+ mTierIndexes = new HashMap<>(V.length + 1, 1f);
+ assert mCachedRecipes != null;
+ Iterator<CachedDefaultRecipe> iterator = Objects.requireNonNull(mCachedRecipes.get())
+ .iterator();
+
+ int index = 0;
+ int minIndex = 0;
+ int maxIndex = -1;
+ byte previousTier = -1;
+ byte lowestTier = 0;
+ while (iterator.hasNext()) {
+ CachedDefaultRecipe recipe = iterator.next();
+ byte recipeTier = GT_Utility
+ .getTier(recipe.mRecipe.mEUt / GT_NEI_DefaultHandler.this.recipeMap.getAmperage());
+ if (recipeTier != previousTier) {
+ if (maxIndex != -1) {
+ mTierIndexes.put(previousTier, Range.between(minIndex, maxIndex));
+ } else {
+ lowestTier = recipeTier;
+ }
+ minIndex = index;
+ previousTier = recipeTier;
+ }
+ maxIndex = index;
+ index++;
+ if (!iterator.hasNext()) {
+ mTierIndexes.put(recipeTier, Range.between(minIndex, maxIndex));
+ mTierRange = Range.between(lowestTier, recipeTier);
+ }
+ }
+ }
+
+ private int getLowIndexForTier(byte lowerTier) {
+ byte lowTier = (byte) Math.max(mTierRange.getMinimum(), lowerTier);
+ while (mTierIndexes.get(lowTier) == null) {
+ lowTier++;
+ }
+ return mTierIndexes.get(lowTier)
+ .getMinimum();
+ }
+
+ private int getHighIndexForTier(byte upperTier) {
+ byte highTier = (byte) Math.min(mTierRange.getMaximum(), upperTier);
+ while (mTierIndexes.get(highTier) == null) {
+ highTier--;
+ }
+ return mTierIndexes.get(highTier)
+ .getMaximum();
+ }
+ }
+}
diff --git a/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java b/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java
new file mode 100644
index 0000000000..bc1c6b5630
--- /dev/null
+++ b/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java
@@ -0,0 +1,54 @@
+package gregtech.nei;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.util.List;
+
+import codechicken.lib.gui.GuiDraw;
+
+public class NEIHandlerAbsoluteTooltip {
+
+ private final Rectangle area;
+ private final String tooltip;
+ private Dimension displaySize;
+
+ public NEIHandlerAbsoluteTooltip(String tooltip, Rectangle area) {
+ this.tooltip = tooltip;
+ this.area = area;
+ }
+
+ public void handleTooltip(List<String> currenttip, int recipeIndex) {
+ displaySize = GuiDraw.displaySize();
+ if (shouldAddTooltip(recipeIndex)) {
+ currenttip.add(tooltip);
+ }
+ }
+
+ private boolean shouldAddTooltip(int recipeIndex) {
+ return isPageFirstRecipe(recipeIndex) && mouseInArea();
+ }
+
+ private boolean mouseInArea() {
+ Point mousePos = getRelMouse();
+ return area.contains(mousePos);
+ }
+
+ private Point getRelMouse() {
+ int ySize = Math.min(Math.max(displaySize.height - 68, 166), 370);
+ int guiLeft = (displaySize.width - 176) / 2;
+ int guiTop = (displaySize.height - ySize) / 2 + 10;
+ Point mousePos = GuiDraw.getMousePosition();
+ return new Point(mousePos.x - guiLeft - 5, mousePos.y - guiTop - 38);
+ }
+
+ private boolean isPageFirstRecipe(int recipe) {
+ int actualRecipesPerPage = getActualRecipesPerPage();
+ return actualRecipesPerPage < 2 || recipe % 2 == 0;
+ }
+
+ private int getActualRecipesPerPage() {
+ int ySize = Math.min(Math.max(displaySize.height - 68, 166), 370);
+ return (ySize - (12 * 3)) / 135;
+ }
+}
diff --git a/src/main/java/gregtech/nei/NEI_GT_Config.java b/src/main/java/gregtech/nei/NEI_GT_Config.java
new file mode 100644
index 0000000000..198f71fdb0
--- /dev/null
+++ b/src/main/java/gregtech/nei/NEI_GT_Config.java
@@ -0,0 +1,183 @@
+package gregtech.nei;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
+
+import codechicken.nei.api.API;
+import codechicken.nei.api.IConfigureNEI;
+import codechicken.nei.event.NEIRegisterHandlerInfosEvent;
+import codechicken.nei.recipe.GuiCraftingRecipe;
+import codechicken.nei.recipe.GuiUsageRecipe;
+import codechicken.nei.recipe.HandlerInfo;
+import codechicken.nei.recipe.TemplateRecipeHandler;
+import cpw.mods.fml.common.event.FMLInterModComms;
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.RecipeMapWorkable;
+import gregtech.api.recipe.RecipeCategory;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.RecipeMaps;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.common.items.GT_MetaGenerated_Item_01;
+import gregtech.common.items.GT_MetaGenerated_Item_02;
+import gregtech.common.items.GT_MetaGenerated_Item_03;
+import gregtech.nei.dumper.BatchModeSupportDumper;
+import gregtech.nei.dumper.InputSeparationSupportDumper;
+import gregtech.nei.dumper.MaterialDumper;
+import gregtech.nei.dumper.MetaItemDumper;
+import gregtech.nei.dumper.MetaTileEntityDumper;
+import gregtech.nei.dumper.RecipeLockingSupportDumper;
+import gregtech.nei.dumper.VoidProtectionSupportDumper;
+
+public class NEI_GT_Config implements IConfigureNEI {
+
+ /**
+ * This map determines the order in which NEI handlers will be registered and displayed in tabs.
+ *
+ * <p>
+ * Handlers will be displayed in ascending order of integer value. Any recipe map that is not present in this map
+ * will be assigned a value of 0. Negative values are fine.
+ */
+ private static final ImmutableMap<RecipeMap<?>, Integer> RECIPE_MAP_ORDERING = ImmutableMap
+ .<RecipeMap<?>, Integer>builder()
+ .put(RecipeMaps.assemblylineVisualRecipes, 1)
+ .put(RecipeMaps.scannerFakeRecipes, 2)
+ .build();
+
+ private static final Comparator<GT_NEI_DefaultHandler> RECIPE_MAP_HANDLER_COMPARATOR = Comparator
+ .comparingInt(handler -> RECIPE_MAP_ORDERING.getOrDefault(handler.getRecipeMap(), 0));
+
+ private static ListMultimap<RecipeCategory, RecipeMapWorkable> RECIPE_CATALYST_INDEX;
+
+ public static boolean sIsAdded = true;
+
+ private static void addHandler(TemplateRecipeHandler handler) {
+ FMLInterModComms.sendRuntimeMessage(
+ GT_Values.GT,
+ "NEIPlugins",
+ "register-crafting-handler",
+ "gregtech@" + handler.getRecipeName() + "@" + handler.getOverlayIdentifier());
+ GuiCraftingRecipe.craftinghandlers.add(handler);
+ GuiUsageRecipe.usagehandlers.add(handler);
+ }
+
+ @Override
+ public void loadConfig() {
+ sIsAdded = false;
+ registerHandlers();
+ registerCatalysts();
+ registerItemEntries();
+ registerDumpers();
+ sIsAdded = true;
+ }
+
+ private void registerHandlers() {
+ RecipeCategory.ALL_RECIPE_CATEGORIES.values()
+ .stream()
+ .filter(
+ recipeCategory -> recipeCategory.recipeMap.getFrontend()
+ .getNEIProperties().registerNEI)
+ .map(GT_NEI_DefaultHandler::new)
+ .sorted(RECIPE_MAP_HANDLER_COMPARATOR)
+ .forEach(NEI_GT_Config::addHandler);
+ }
+
+ private void registerCatalysts() {
+ for (Map.Entry<RecipeCategory, Collection<RecipeMapWorkable>> entry : RECIPE_CATALYST_INDEX.asMap()
+ .entrySet()) {
+ entry.getValue()
+ .forEach(
+ recipeMapWorkable -> API.addRecipeCatalyst(
+ recipeMapWorkable.getStackForm(1),
+ entry.getKey().unlocalizedName,
+ recipeMapWorkable.getRecipeCatalystPriority()));
+ }
+ API.addRecipeCatalyst(
+ GT_ModHandler.getIC2Item("nuclearReactor", 1, null),
+ RecipeMaps.ic2NuclearFakeRecipes.unlocalizedName);
+ }
+
+ private void registerItemEntries() {
+ API.addItemListEntry(ItemList.VOLUMETRIC_FLASK.get(1));
+ }
+
+ private void registerDumpers() {
+ API.addOption(new MetaTileEntityDumper());
+ API.addOption(new MaterialDumper());
+ API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_01.INSTANCE, "metaitem01"));
+ API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_02.INSTANCE, "metaitem02"));
+ API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_03.INSTANCE, "metaitem03"));
+ API.addOption(new VoidProtectionSupportDumper());
+ API.addOption(new InputSeparationSupportDumper());
+ API.addOption(new BatchModeSupportDumper());
+ API.addOption(new RecipeLockingSupportDumper());
+ }
+
+ @SubscribeEvent
+ public void registerHandlerInfo(NEIRegisterHandlerInfosEvent event) {
+ if (RECIPE_CATALYST_INDEX == null) {
+ // This method will be called earlier than #loadConfig
+ generateRecipeCatalystIndex();
+ }
+ RecipeCategory.ALL_RECIPE_CATEGORIES.values()
+ .forEach(recipeCategory -> {
+ HandlerInfo.Builder builder = createHandlerInfoBuilderTemplate(recipeCategory);
+ HandlerInfo handlerInfo;
+ if (recipeCategory.handlerInfoCreator != null) {
+ handlerInfo = recipeCategory.handlerInfoCreator.apply(builder)
+ .build();
+ } else {
+ // Infer icon from recipe catalysts
+ RECIPE_CATALYST_INDEX.get(recipeCategory)
+ .stream()
+ .findFirst()
+ .ifPresent(catalyst -> builder.setDisplayStack(catalyst.getStackForm(1)));
+ handlerInfo = builder.build();
+ }
+ event.registerHandlerInfo(handlerInfo);
+ });
+ }
+
+ private HandlerInfo.Builder createHandlerInfoBuilderTemplate(RecipeCategory recipeCategory) {
+ return new HandlerInfo.Builder(
+ recipeCategory.unlocalizedName,
+ recipeCategory.ownerMod.getName(),
+ recipeCategory.ownerMod.getModId()).setShiftY(6)
+ .setHeight(135)
+ .setMaxRecipesPerPage(2);
+ }
+
+ private static void generateRecipeCatalystIndex() {
+ ImmutableListMultimap.Builder<RecipeCategory, RecipeMapWorkable> builder = new ImmutableListMultimap.Builder<>();
+ builder
+ .orderValuesBy(Comparator.comparing(recipeMapWorkable -> -recipeMapWorkable.getRecipeCatalystPriority()));
+ for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) {
+ IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i];
+ if (!(mte instanceof RecipeMapWorkable recipeMapWorkable)) continue;
+ for (RecipeMap<?> recipeMap : recipeMapWorkable.getAvailableRecipeMaps()) {
+ for (RecipeCategory recipeCategory : recipeMap.getAssociatedCategories()) {
+ builder.put(recipeCategory, recipeMapWorkable);
+ }
+ }
+ }
+ RECIPE_CATALYST_INDEX = builder.build();
+ }
+
+ @Override
+ public String getName() {
+ return "GregTech NEI Plugin";
+ }
+
+ @Override
+ public String getVersion() {
+ return "(5.03a)";
+ }
+}
diff --git a/src/main/java/gregtech/nei/RecipeDisplayInfo.java b/src/main/java/gregtech/nei/RecipeDisplayInfo.java
new file mode 100644
index 0000000000..f9cc1a9a8c
--- /dev/null
+++ b/src/main/java/gregtech/nei/RecipeDisplayInfo.java
@@ -0,0 +1,99 @@
+package gregtech.nei;
+
+import static gregtech.api.util.GT_Utility.isStringInvalid;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.client.Minecraft;
+
+import gregtech.api.objects.overclockdescriber.OverclockDescriber;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.util.FieldsAreNonnullByDefault;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+
+/**
+ * Holds info used for drawing descriptions on NEI.
+ */
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+@FieldsAreNonnullByDefault
+public class RecipeDisplayInfo {
+
+ /**
+ * Recipe to show description.
+ */
+ public final GT_Recipe recipe;
+
+ /**
+ * RecipeMap the recipe belongs to.
+ */
+ public final RecipeMap<?> recipeMap;
+
+ /**
+ * When user looks up usage for machine, NEI will show all the recipes that the machine can process, taking tier of
+ * the machine into consideration. This object can be used to show info around overclocked EU/t and duration.
+ */
+ public final OverclockDescriber overclockDescriber;
+
+ /**
+ * Pre-built overclock calculator, used for drawing OC information. Do not calculate it again.
+ */
+ public final GT_OverclockCalculator calculator;
+
+ /**
+ * Current Y position for drawing description.
+ */
+ private int yPos;
+
+ private final int neiTextColorOverride;
+
+ RecipeDisplayInfo(GT_Recipe recipe, RecipeMap<?> recipeMap, OverclockDescriber overclockDescriber,
+ GT_OverclockCalculator calculator, int descriptionYOffset, int neiTextColorOverride) {
+ this.recipe = recipe;
+ this.recipeMap = recipeMap;
+ this.overclockDescriber = overclockDescriber;
+ this.calculator = calculator;
+ this.yPos = descriptionYOffset;
+ this.neiTextColorOverride = neiTextColorOverride;
+ }
+
+ /**
+ * Draws text.
+ */
+ public void drawText(@Nullable String text) {
+ drawText(text, 10);
+ }
+
+ /**
+ * Draws text.
+ *
+ * @param yShift y position to shift after this text
+ */
+ public void drawText(@Nullable String text, int yShift) {
+ drawText(text, 5, yShift);
+ }
+
+ /**
+ * Draws text.
+ *
+ * @param xStart x position to start drawing
+ * @param yShift y position to shift after this text
+ */
+ public void drawText(@Nullable String text, int xStart, int yShift) {
+ if (isStringInvalid(text)) return;
+ Minecraft.getMinecraft().fontRenderer
+ .drawString(text, xStart, yPos, neiTextColorOverride != -1 ? neiTextColorOverride : 0x000000);
+ yPos += yShift;
+ }
+
+ public void drawTextMultipleLines(List<String> texts) {
+ for (String text : texts) {
+ drawText(text, 10);
+ }
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java b/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java
new file mode 100644
index 0000000000..ea0168073a
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java
@@ -0,0 +1,10 @@
+package gregtech.nei.dumper;
+
+import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures;
+
+public class BatchModeSupportDumper extends MultiBlockFeatureSupportDumper {
+
+ public BatchModeSupportDumper() {
+ super("batch_mode", ControllerWithOptionalFeatures::supportsBatchMode);
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java b/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java
new file mode 100644
index 0000000000..0c2f20ac22
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java
@@ -0,0 +1,61 @@
+package gregtech.nei.dumper;
+
+import static gregtech.api.enums.Mods.NewHorizonsCoreMod;
+
+import net.minecraft.util.ChatComponentTranslation;
+import net.minecraft.util.ChatStyle;
+import net.minecraft.util.EnumChatFormatting;
+
+import codechicken.nei.NEIClientUtils;
+import codechicken.nei.config.DataDumper;
+
+public abstract class GregTechIDDumper extends DataDumper {
+
+ public GregTechIDDumper(String name) {
+ super("tools.dump.gt5u." + name);
+ }
+
+ @Override
+ public Iterable<String[]> dump(int modeInt) {
+ return dump(getMode(modeInt));
+ }
+
+ protected abstract Iterable<String[]> dump(Mode mode);
+
+ @Override
+ public String modeButtonText() {
+ return NEIClientUtils.lang.translate("options.tools.dump.gt5u.mode." + getMode());
+ }
+
+ @Override
+ public void dumpFile() {
+ super.dumpFile();
+ logWarn();
+ }
+
+ protected void super$dumpFile() {
+ super.dumpFile();
+ }
+
+ protected void logWarn() {
+ if (!NewHorizonsCoreMod.isModLoaded()) {
+ NEIClientUtils.printChatMessage(
+ new ChatComponentTranslation("nei.options.tools.dump.gt5u.warn_env")
+ .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.DARK_RED)));
+ }
+ }
+
+ @Override
+ public int modeCount() {
+ return Mode.values().length;
+ }
+
+ protected Mode getMode(int modeInt) {
+ return Mode.values()[modeInt];
+ }
+
+ protected enum Mode {
+ FREE,
+ USED
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java b/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java
new file mode 100644
index 0000000000..5b0d293827
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java
@@ -0,0 +1,10 @@
+package gregtech.nei.dumper;
+
+import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures;
+
+public class InputSeparationSupportDumper extends MultiBlockFeatureSupportDumper {
+
+ public InputSeparationSupportDumper() {
+ super("input_separation", ControllerWithOptionalFeatures::supportsInputSeparation);
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/MaterialDumper.java b/src/main/java/gregtech/nei/dumper/MaterialDumper.java
new file mode 100644
index 0000000000..3234779de8
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/MaterialDumper.java
@@ -0,0 +1,39 @@
+package gregtech.nei.dumper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.Materials;
+
+public class MaterialDumper extends GregTechIDDumper {
+
+ public MaterialDumper() {
+ super("material");
+ }
+
+ @Override
+ public String[] header() {
+ return new String[] { "id", "name", };
+ }
+
+ @Override
+ protected Iterable<String[]> dump(Mode mode) {
+ List<String[]> dump = new ArrayList<>();
+ Map<Integer, Materials> idMap = Arrays.stream(GregTech_API.sGeneratedMaterials)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(m -> m.mMetaItemSubID, m -> m));
+ for (int i = 0; i < 1000; i++) {
+ if (mode == Mode.FREE && !idMap.containsKey(i)) {
+ dump.add(new String[] { String.valueOf(i), "", });
+ } else if (mode == Mode.USED && idMap.containsKey(i)) {
+ dump.add(new String[] { String.valueOf(i), idMap.get(i).mName, });
+ }
+ }
+ return dump;
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/MetaItemDumper.java b/src/main/java/gregtech/nei/dumper/MetaItemDumper.java
new file mode 100644
index 0000000000..519af48e93
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/MetaItemDumper.java
@@ -0,0 +1,60 @@
+package gregtech.nei.dumper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ChatComponentText;
+
+import codechicken.nei.NEIClientUtils;
+import gregtech.api.items.GT_MetaGenerated_Item;
+
+public class MetaItemDumper extends GregTechIDDumper {
+
+ private final GT_MetaGenerated_Item item;
+ private final String nameSuffix;
+
+ public MetaItemDumper(GT_MetaGenerated_Item item, String name) {
+ super(name);
+ this.nameSuffix = name;
+ this.item = item;
+ }
+
+ @Override
+ public String[] header() {
+ return new String[] { "id", "stackName", "metaID" };
+ }
+
+ @Override
+ protected Iterable<String[]> dump(Mode mode) {
+ List<String[]> list = new ArrayList<>();
+ for (int i = 0; i < item.mItemAmount; i++) {
+ int metaID = item.mOffset + i;
+ boolean generated = item.mEnabledItems.get(i);
+ if (mode == Mode.FREE && !generated) {
+ list.add(new String[] { String.valueOf(i), "", String.valueOf(metaID), });
+ } else if (mode == Mode.USED && generated) {
+ list.add(
+ new String[] { String.valueOf(i), new ItemStack(item, 1, metaID).getDisplayName(),
+ String.valueOf(metaID), });
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public void dumpFile() {
+ super.super$dumpFile();
+ NEIClientUtils.printChatMessage(
+ new ChatComponentText(String.format("mOffset: %s, mItemAmount: %s", item.mOffset, item.mItemAmount)));
+ logWarn();
+ }
+
+ @Override
+ public String translateN(String s, Object... args) {
+ if (name.equals(s) || (name + "s").equals(s)) {
+ return nameSuffix;
+ }
+ return super.translateN(s, args);
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java b/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java
new file mode 100644
index 0000000000..ee30cdfb8a
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java
@@ -0,0 +1,37 @@
+package gregtech.nei.dumper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+
+public class MetaTileEntityDumper extends GregTechIDDumper {
+
+ public MetaTileEntityDumper() {
+ super("metatileentity");
+ }
+
+ @Override
+ public String[] header() {
+ return new String[] { "id", "stackName", "className", };
+ }
+
+ @Override
+ protected Iterable<String[]> dump(Mode mode) {
+ List<String[]> list = new ArrayList<>();
+ for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) {
+ IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i];
+ if (mode == Mode.FREE && mte == null) {
+ list.add(new String[] { String.valueOf(i), "", "", });
+ } else if (mode == Mode.USED && mte != null) {
+ list.add(
+ new String[] { String.valueOf(i), mte.getStackForm(1)
+ .getDisplayName(),
+ mte.getClass()
+ .getSimpleName() });
+ }
+ }
+ return list;
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java b/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java
new file mode 100644
index 0000000000..dc378ff9ad
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java
@@ -0,0 +1,46 @@
+package gregtech.nei.dumper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import codechicken.nei.config.DataDumper;
+import gregtech.api.GregTech_API;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures;
+
+public abstract class MultiBlockFeatureSupportDumper extends DataDumper {
+
+ private final Function<ControllerWithOptionalFeatures, Boolean> isFeatureSupported;
+
+ public MultiBlockFeatureSupportDumper(String name,
+ Function<ControllerWithOptionalFeatures, Boolean> isFeatureSupported) {
+ super("tools.dump.gt5u." + name);
+ this.isFeatureSupported = isFeatureSupported;
+ }
+
+ @Override
+ public String[] header() {
+ return new String[] { "className" };
+ }
+
+ @Override
+ public Iterable<String[]> dump(int mode) {
+ List<String[]> list = new ArrayList<>();
+ for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) {
+ IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i];
+ if (!(mte instanceof ControllerWithOptionalFeatures controller)) continue;
+ if (!isFeatureSupported.apply(controller)) {
+ list.add(
+ new String[] { controller.getClass()
+ .getName() });
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public int modeCount() {
+ return 1;
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java b/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java
new file mode 100644
index 0000000000..05deba6c39
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java
@@ -0,0 +1,10 @@
+package gregtech.nei.dumper;
+
+import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures;
+
+public class RecipeLockingSupportDumper extends MultiBlockFeatureSupportDumper {
+
+ public RecipeLockingSupportDumper() {
+ super("recipe_locking", ControllerWithOptionalFeatures::supportsSingleRecipeLocking);
+ }
+}
diff --git a/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java b/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java
new file mode 100644
index 0000000000..b48efb2cdc
--- /dev/null
+++ b/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java
@@ -0,0 +1,10 @@
+package gregtech.nei.dumper;
+
+import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures;
+
+public class VoidProtectionSupportDumper extends MultiBlockFeatureSupportDumper {
+
+ public VoidProtectionSupportDumper() {
+ super("void_protection", ControllerWithOptionalFeatures::supportsVoidProtection);
+ }
+}
diff --git a/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java
new file mode 100644
index 0000000000..1c4d486319
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java
@@ -0,0 +1,36 @@
+package gregtech.nei.formatter;
+
+import static gregtech.api.util.GT_Utility.trans;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import gregtech.GT_Mod;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class DefaultSpecialValueFormatter implements INEISpecialInfoFormatter {
+
+ public static DefaultSpecialValueFormatter INSTANCE = new DefaultSpecialValueFormatter();
+
+ @Override
+ public List<String> format(RecipeDisplayInfo recipeInfo) {
+ int specialValue = recipeInfo.recipe.mSpecialValue;
+ if (specialValue == -100 && GT_Mod.gregtechproxy.mLowGravProcessing) {
+ return Collections.singletonList(trans("159", "Needs Low Gravity"));
+ } else if (specialValue == -200 && GT_Mod.gregtechproxy.mEnableCleanroom) {
+ return Collections.singletonList(trans("160", "Needs Cleanroom"));
+ } else if (specialValue == -201) {
+ return Collections.singletonList(trans("206", "Scan for Assembly Line"));
+ } else if (specialValue == -300 && GT_Mod.gregtechproxy.mEnableCleanroom) {
+ return Collections.singletonList(trans("160.1", "Needs Cleanroom & LowGrav"));
+ } else if (specialValue == -400) {
+ return Collections.singletonList(trans("216", "Deprecated Recipe"));
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java
new file mode 100644
index 0000000000..dcfe2617dd
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java
@@ -0,0 +1,27 @@
+package gregtech.nei.formatter;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.util.StatCollector;
+
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class FuelSpecialValueFormatter implements INEISpecialInfoFormatter {
+
+ public static FuelSpecialValueFormatter INSTANCE = new FuelSpecialValueFormatter();
+
+ @Override
+ public List<String> format(RecipeDisplayInfo recipeInfo) {
+ return Collections.singletonList(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.nei.fuel",
+ GT_Utility.formatNumbers(recipeInfo.recipe.mSpecialValue * 1000L)));
+ }
+}
diff --git a/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java
new file mode 100644
index 0000000000..77cd41b343
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java
@@ -0,0 +1,59 @@
+package gregtech.nei.formatter;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.util.StatCollector;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class FusionSpecialValueFormatter implements INEISpecialInfoFormatter {
+
+ public static final FusionSpecialValueFormatter INSTANCE = new FusionSpecialValueFormatter();
+ private static final long M = 1000000;
+
+ @Override
+ public List<String> format(RecipeDisplayInfo recipeInfo) {
+ int euToStart = recipeInfo.recipe.mSpecialValue;
+ int voltage = recipeInfo.recipe.mEUt;
+ int tier = getFusionTier(euToStart, voltage);
+
+ return Collections.singletonList(
+ StatCollector.translateToLocalFormatted("GT5U.nei.start_eu", GT_Utility.formatNumbers(euToStart), tier));
+ }
+
+ public static int getFusionTier(long startupPower, long voltage) {
+ int tier;
+ if (startupPower <= 10 * M * 16) {
+ tier = 1;
+ } else if (startupPower <= 20 * M * 16) {
+ tier = 2;
+ } else if (startupPower <= 40 * M * 16) {
+ tier = 3;
+ } else if (startupPower <= 320 * M * 16) {
+ tier = 4;
+ } else {
+ tier = 5;
+ }
+
+ if (voltage <= GT_Values.V[6]) {
+ // no-op
+ } else if (voltage <= GT_Values.V[7]) {
+ tier = Math.max(tier, 2);
+ } else if (voltage <= GT_Values.V[8]) {
+ tier = Math.max(tier, 3);
+ } else if (voltage <= GT_Values.V[9]) {
+ tier = Math.max(tier, 4);
+ } else {
+ tier = 5;
+ }
+ return tier;
+ }
+}
diff --git a/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java
new file mode 100644
index 0000000000..f5c17a1163
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java
@@ -0,0 +1,30 @@
+package gregtech.nei.formatter;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.util.StatCollector;
+
+import gregtech.api.enums.HeatingCoilLevel;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class HeatingCoilSpecialValueFormatter implements INEISpecialInfoFormatter {
+
+ public static final HeatingCoilSpecialValueFormatter INSTANCE = new HeatingCoilSpecialValueFormatter();
+
+ @Override
+ public List<String> format(RecipeDisplayInfo recipeInfo) {
+ int heat = recipeInfo.recipe.mSpecialValue;
+ return Collections.singletonList(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.nei.heat_capacity",
+ GT_Utility.formatNumbers(heat),
+ HeatingCoilLevel.getDisplayNameFromHeat(heat, false)));
+ }
+}
diff --git a/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java b/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java
new file mode 100644
index 0000000000..21228240d4
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java
@@ -0,0 +1,24 @@
+package gregtech.nei.formatter;
+
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+/**
+ * Getter for description for {@link gregtech.api.util.GT_Recipe#mSpecialValue} etc. that will be drawn on NEI.
+ */
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+@FunctionalInterface
+public interface INEISpecialInfoFormatter {
+
+ /**
+ * @param recipeInfo Recipe info to draw description. You can retrieve special value with
+ * {@code recipeInfo.recipe.mSpecialValue}.
+ * @return List of strings containing info for special value etc.
+ */
+ List<String> format(RecipeDisplayInfo recipeInfo);
+}
diff --git a/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java
new file mode 100644
index 0000000000..8f2098c0a9
--- /dev/null
+++ b/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java
@@ -0,0 +1,49 @@
+package gregtech.nei.formatter;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import net.minecraft.util.StatCollector;
+
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+import gregtech.nei.RecipeDisplayInfo;
+
+/**
+ * Simple formatter for recipe's special value.
+ */
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class SimpleSpecialValueFormatter implements INEISpecialInfoFormatter {
+
+ @Nullable
+ private final String translationKey;
+ private final int multiplier;
+
+ /**
+ * @param translationKey Localization key to format
+ * @param multiplier Number to multiply to special value for display
+ */
+ public SimpleSpecialValueFormatter(@Nullable String translationKey, int multiplier) {
+ this.translationKey = translationKey;
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * @param translationKey Localization key to format
+ */
+ public SimpleSpecialValueFormatter(@Nullable String translationKey) {
+ this(translationKey, 1);
+ }
+
+ @Override
+ public List<String> format(RecipeDisplayInfo recipeInfo) {
+ return Collections.singletonList(
+ StatCollector.translateToLocalFormatted(
+ translationKey,
+ GT_Utility.formatNumbers((long) recipeInfo.recipe.mSpecialValue * multiplier)));
+ }
+}