Compare commits

...
Author SHA1 Message Date
Wilhelm Schuster 5b9c27e253 Fix potential NPE when calling ItemMobSpawner#loadSpawners. (#405) 2023-07-16 14:29:31 +02:00
Raven Szewczyk d613d06471 Attempt to fix CME when opening recipes immediately after load (#403) 2023-07-16 09:00:35 +01:00
65ffbad491 Show a full recipe widget in item tooltips for bookmarked recipes (#398)
* Update buildscript

* Show a full recipe widget in item tooltips for bookmarked recipes

* Make recipe tooltips configurable and only show in the bookmarks panel

* Fix layout relative to tooltip boxes

* Fix wildcard import

* spotlessApply (#401)

Co-authored-by: GitHub GTNH Actions <>

* Fix handler tooltip offsets for new&legacy handlers

* Fix render order to draw crafting tooltips on top of container manager buttons

* Fix wrong mouse Y half-point calculation

* Allow saving recipes with empty item ingredient lists to bookmarks (e.g. fluid-only)

* Fix waila vs. non-waila tooltip offsets

Co-authored-by: Wilhelm Schuster <[email protected]>

---------

Co-authored-by: Wilhelm Schuster <[email protected]>
2023-07-10 17:38:07 +01:00
Wilhelm Schuster 885609720e Extend height hack for legacy recipe handlers (#395)
* Store heighthackhandlers.cfg as an internal asset instead of an array of strings

Is copied into the config directory upon first launch. Also, comments
are now allowed (only from the beginning of the file).

* Store serialhandlers.cfg as an internal asset instead of an array of strings

Is copied into the config directory upon first launch. Also, comments
are now allowed (only from the beginning of the file).

Removes GTNH-specific code to be replaced with a configuration file.

* Extend the height hack to also cover guiTop and getRecipePosition

Certain legacy recipe handlers hardcode even more of the NEI recipe
screen layout, requiring only the singular recipe handler switcher
widget at the top, whereas this improved NEI moves the recipe pager to
the top as well expecting recipe handler to move down another 16px. One
such mod that breaks is Immersive Engineering. To fix it we move the top
edge of the gui (guiTop) down by 16px so that the old gap calculation
works correctly. This required special handling in getRecipePosition()
and refreshSlots().

* Fix Immersive Engineering recipe handlers

* Fix Ex Nihilo recipe handlers

* Fix Thermal Expansion recipe handlers

These handlers expect only two recipes to be displayed at a time. This can only be fixed through asm AFAICT.
2023-07-08 23:23:19 +02:00
20 changed files with 457 additions and 136 deletions

No files matched your search

-7
View File
@@ -27,13 +27,6 @@ If you have issues with NEI outside of the GTNH modpack you may report them in t
* Remove TMI style
* Removed inventory Load/Save state
## Development
Before launching, you need to add
```
-Dfml.coreMods.load=codechicken.nei.asm.NEICorePlugin
```
as a command line argument to the VM.
## License
GTNH Modifications Copyright (c) 2019-2022 mitchej123 and the GTNH Team
+3
View File
@@ -26,3 +26,6 @@ blowdryerSetup {
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.2.0')
//devLocal '.' // Use this when testing config updates locally
}
rootProject.name = "NotEnoughItems"
@@ -84,6 +84,10 @@ public class BookmarkPanel extends PanelWidget {
protected boolean[] todoSpaces;
protected int todoItemsCount = 0;
public BookmarkGrid() {
this.setShowRecipeTooltips(true);
}
public void setViewMode(BookmarkViewMode mode) {
if (viewMode == mode) {
return;
@@ -464,7 +468,7 @@ public class BookmarkPanel extends PanelWidget {
final ItemStack normalizedA = StackInfo.loadFromNBT(nbTagA);
BookmarkRecipeId recipeId = null;
if (handlerName != "" && ingredients != null && !ingredients.isEmpty()) {
if (handlerName != "" && ingredients != null) {
recipeId = new BookmarkRecipeId(handlerName, ingredients);
}
@@ -474,7 +478,7 @@ public class BookmarkPanel extends PanelWidget {
BGrid.removeRecipe(idx, saveIngredients);
} else {
if (saveIngredients && handlerName != "" && ingredients != null && !ingredients.isEmpty()) {
if (saveIngredients && handlerName != "" && ingredients != null) {
final Map<NBTTagCompound, Integer> unique = new HashMap<>();
final ArrayList<NBTTagCompound> sorted = new ArrayList<>();
@@ -9,9 +9,6 @@ import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -48,20 +45,12 @@ import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.StackInfo;
import cpw.mods.fml.client.CustomModLoadingErrorDisplayException;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
public class ClientHandler {
private static String[] defaultSerialHandlers = {
"WayofTime.alchemicalWizardry.client.nei.NEIAlchemyRecipeHandler" };
private static String[] defaultHeightHackHandlerRegex = { "buildcraft.compat.nei.*",
"cofh.thermalexpansion.plugins.nei.handlers.*", "crazypants.enderio.nei.*",
"forestry.factory.recipes.nei.*", "ic2.neiIntegration.core.recipehandler.*", "mariculture.plugins.nei.*",
"redgear.brewcraft.plugins.nei.*", "tconstruct.plugins.nei.*",
"WayofTime.alchemicalWizardry.client.nei.*", };
private static String[] defaultHandlerOrdering = {
"# Each line in this file should either be a comment (starts with '#') or an ordering.",
"# Ordering lines are <handler ID>,<ordering number>.",
@@ -165,16 +154,19 @@ public class ClientHandler {
if (!file.exists()) {
try (FileWriter writer = new FileWriter(file)) {
NEIClientConfig.logger.info("Creating default serial handlers list {}", file);
Collection<String> toSave = Loader.isModLoaded("dreamcraft") ? Collections.singletonList("")
: Arrays.asList(defaultSerialHandlers);
IOUtils.writeLines(toSave, "\n", writer);
URL defaultSerialHandlersResource = ClientHandler.class
.getResource("/assets/nei/cfg/serialhandlers.cfg");
if (defaultSerialHandlersResource != null) {
IOUtils.copy(defaultSerialHandlersResource.openStream(), writer);
}
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to save default serial handlers list to file {}", file, e);
}
}
try (FileReader reader = new FileReader(file)) {
NEIClientConfig.logger.info("Loading serial handlers from file {}", file);
NEIClientConfig.serialHandlers = new HashSet<>(IOUtils.readLines(reader));
NEIClientConfig.serialHandlers = IOUtils.readLines(reader).stream().filter((line) -> !line.startsWith("#"))
.collect(Collectors.toCollection(HashSet::new));
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to load serial handlers from file {}", file, e);
}
@@ -185,8 +177,11 @@ public class ClientHandler {
if (!file.exists()) {
try (FileWriter writer = new FileWriter(file)) {
NEIClientConfig.logger.info("Creating default height hack handlers list {}", file);
Collection<String> toSave = Arrays.asList(defaultHeightHackHandlerRegex);
IOUtils.writeLines(toSave, "\n", writer);
URL defaultHeightHackHandlersResource = ClientHandler.class
.getResource("/assets/nei/cfg/heighthackhandlers.cfg");
if (defaultHeightHackHandlersResource != null) {
IOUtils.copy(defaultHeightHackHandlersResource.openStream(), writer);
}
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to save default height hack handlers list to file {}", file, e);
}
@@ -194,7 +189,8 @@ public class ClientHandler {
try (FileReader reader = new FileReader(file)) {
NEIClientConfig.logger.info("Loading height hack handlers from file {}", file);
NEIClientConfig.heightHackHandlerRegex = IOUtils.readLines(reader).stream().map(Pattern::compile)
NEIClientConfig.heightHackHandlerRegex = IOUtils.readLines(reader).stream()
.filter((line) -> !line.startsWith("#")).map(Pattern::compile)
.collect(Collectors.toCollection(HashSet::new));
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to load height hack handlers from file {}", file, e);
@@ -22,8 +22,8 @@ import net.minecraftforge.client.MinecraftForgeClient;
public class ItemMobSpawner extends ItemBlock {
private static Map<Integer, EntityLiving> entityHashMap;
private static Map<Integer, String> IDtoNameMap;
private static final Map<Integer, EntityLiving> entityHashMap = new HashMap<>();
private static final Map<Integer, String> IDtoNameMap = new HashMap<>();
public static int idPig;
private static boolean loaded;
@@ -32,9 +32,6 @@ public class ItemMobSpawner extends ItemBlock {
hasSubtypes = true;
MinecraftForgeClient.registerItemRenderer(this, new SpawnerRenderer());
entityHashMap = new HashMap<>();
IDtoNameMap = new HashMap<>();
}
/**
@@ -1,19 +1,24 @@
package codechicken.nei;
import static codechicken.lib.gui.GuiDraw.drawRect;
import static codechicken.lib.gui.GuiDraw.getMousePosition;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11;
@@ -22,6 +27,9 @@ import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.ItemPanel.ItemPanelSlot;
import codechicken.nei.api.GuiInfo;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.BookmarkRecipeId;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.StackInfo;
public class ItemsGrid {
@@ -43,6 +51,7 @@ public class ItemsGrid {
protected int rows;
protected int columns;
protected boolean showRecipeTooltips = false;
protected boolean[] invalidSlotMap;
@@ -150,6 +159,10 @@ public class ItemsGrid {
refreshBuffer = true;
}
public void setShowRecipeTooltips(boolean show) {
this.showRecipeTooltips = show;
}
public void refresh(GuiContainer gui) {
updateGuiOverlapSlots(gui);
shiftPage(0);
@@ -244,6 +257,120 @@ public class ItemsGrid {
}
}
private int recipeTooltipSlotIdx = -1;
private int recipeTooltipLines = 2;
private Runnable recipeTooltipUpdater = null;
private GuiRecipe<?> recipeTooltipGui = null;
public void update() {
if (recipeTooltipUpdater != null) {
recipeTooltipUpdater.run();
recipeTooltipUpdater = null;
}
if (recipeTooltipGui != null) {
recipeTooltipGui.updateAsTooltip();
}
}
private static <T> List<T> listOrEmptyList(final List<T> listOrNull) {
return listOrNull == null ? Collections.emptyList() : listOrNull;
}
private void drawRecipeTooltip(int mousex, int mousey, List<String> itemTooltip) {
if (!NEIClientConfig.isLoaded()) {
return;
}
final Minecraft mc = Minecraft.getMinecraft();
ItemPanelSlot focused = getSlotMouseOver(mousex, mousey);
if (focused == null) {
recipeTooltipSlotIdx = -1;
recipeTooltipGui = null;
return;
}
final List<Integer> mask = getMask();
int slotIdx = -1;
for (int i = 0; i < mask.size(); i++) {
if (mask.get(i) == null) {
continue;
}
if (focused.slotIndex != mask.get(i)) {
continue;
}
slotIdx = i;
break;
}
if (slotIdx == -1) {
recipeTooltipSlotIdx = -1;
recipeTooltipGui = null;
return;
}
final Point mouseover = getMousePosition();
final ItemPanelSlot panelSlot = ItemPanels.bookmarkPanel.getSlotMouseOver(mouseover.x, mouseover.y);
final BookmarkRecipeId recipeId;
if (panelSlot != null) {
recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(panelSlot.slotIndex);
} else {
recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(focused.item);
}
if (recipeId == null) {
return;
}
if (slotIdx != recipeTooltipSlotIdx) {
recipeTooltipSlotIdx = slotIdx;
recipeTooltipGui = null;
recipeTooltipUpdater = () -> {
recipeTooltipGui = GuiCraftingRecipe.createRecipeGui("item", false, false, false, focused.item);
if (recipeTooltipGui != null) {
recipeTooltipGui.limitToOneRecipe();
recipeTooltipGui.initGui(true);
recipeTooltipGui.guiTop = 0;
recipeTooltipGui.guiLeft = 0;
}
recipeTooltipLines = Math.max(1, itemTooltip.size());
};
}
if (recipeTooltipGui == null) {
return;
}
GL11.glPushMatrix();
final float tooltipYOffset;
if (mousey - marginTop > height / 2) {
tooltipYOffset = mousey - recipeTooltipGui.getHeightAsWidget() + 8;
} else {
tooltipYOffset = mousey + ((recipeTooltipLines < 2) ? 1 : 3 + ((recipeTooltipLines - 1) * 10));
}
GL11.glTranslatef(mousex, tooltipYOffset, 500);
final GuiContainer gui;
if (mc.currentScreen instanceof GuiRecipe) {
gui = ((GuiRecipe<?>) mc.currentScreen).firstGui;
} else if (mc.currentScreen instanceof GuiContainer) {
gui = (GuiContainer) mc.currentScreen;
} else {
gui = null;
}
recipeTooltipGui.drawGuiContainerBackgroundLayer(0.0f, -100, -100);
if (recipeTooltipGui.slotcontainer != null) {
@SuppressWarnings("unchecked")
List<Slot> slots = (List<Slot>) recipeTooltipGui.slotcontainer.inventorySlots;
for (Slot slot : slots) {
if (slot != null && slot.getStack() != null) {
GuiContainerManager.drawItem(slot.xDisplayPosition, slot.yDisplayPosition, slot.getStack());
}
}
}
recipeTooltipGui.drawGuiContainerForegroundLayer(-100, -100);
for (GuiButton btn : recipeTooltipGui.getOverlayButtons()) {
btn.drawButton(mc, -100, -100);
}
GL11.glPopMatrix();
}
protected void drawSlotOutline(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) {
if (focused != null && focused.slotIndex == slotIdx) {
drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); // highlight
@@ -329,6 +456,16 @@ public class ItemsGrid {
}
}
public void postDrawTooltips(int mousex, int mousey, List<String> tooltip) {
if (NEIClientConfig.showRecipeTooltips() && showRecipeTooltips) {
try {
drawRecipeTooltip(mousex, mousey, tooltip);
} catch (Exception e) {
NEIClientConfig.logger.warn("Cannot draw recipe tooltip", e);
}
}
}
public void setVisible() {
if (getItems().isEmpty() && getMessageOnEmpty() != null) {
LayoutManager.addWidget(messageLabel);
@@ -280,6 +280,13 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
}
}
@Override
public void postRenderTooltips(GuiContainer gui, int mousex, int mousey, List<String> tooltip) {
if (!isHidden() && isEnabled()) {
for (Widget widget : drawWidgets) widget.postDrawTooltips(mousex, mousey, tooltip);
}
}
@Override
public List<String> handleTooltip(GuiContainer gui, int mousex, int mousey, List<String> currenttip) {
if (!isHidden() && isEnabled() && GuiContainerManager.shouldShowTooltip(gui)) {
@@ -242,6 +242,9 @@ public class NEIClientConfig {
tag.getTag("inventory.bookmarksAnimationEnabled").setComment("REI Style Animation in Bookmarks")
.getBooleanValue(true);
API.addOption(new OptionToggleButton("inventory.bookmarksAnimationEnabled", true));
tag.getTag("inventory.recipeTooltipsEnabled").setComment("Show recipe tooltips in Bookmarks")
.getBooleanValue(true);
API.addOption(new OptionToggleButton("inventory.recipeTooltipsEnabled", true));
tag.getTag("inventory.showRecipeMarker").setComment("Show Recipe Marker").getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.showRecipeMarker", true));
@@ -559,6 +562,10 @@ public class NEIClientConfig {
return getBooleanSetting("inventory.bookmarksAnimationEnabled");
}
public static boolean showRecipeTooltips() {
return getBooleanSetting("inventory.recipeTooltipsEnabled");
}
public static boolean showRecipeMarker() {
return getBooleanSetting("inventory.showRecipeMarker");
}
@@ -1,6 +1,7 @@
package codechicken.nei;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
@@ -171,6 +172,11 @@ public abstract class PanelWidget extends Widget {
}
}
@Override
public void update() {
grid.update();
}
@Override
public void draw(int mousex, int mousey) {
grid.draw(mousex, mousey);
@@ -185,6 +191,11 @@ public abstract class PanelWidget extends Widget {
}
}
@Override
public void postDrawTooltips(int mx, int my, List<String> tooltip) {
grid.postDrawTooltips(mx, my, tooltip);
}
@Override
public void mouseDragged(int mousex, int mousey, int button, long heldTime) {
if (mouseDownSlot >= 0 && draggedStack == null
@@ -14,6 +14,8 @@ public abstract class Widget {
public void postDraw(int mx, int my) {}
public void postDrawTooltips(int mx, int my, List<String> tooltip) {}
public boolean handleClick(int mx, int my, int button) {
return true;
}
@@ -20,6 +20,10 @@ public abstract class ShortcutInputHandler {
public static boolean handleKeyEvent(ItemStack stackover) {
if (!NEIClientConfig.isLoaded()) {
return false;
}
if (NEIClientConfig.isKeyHashDown("gui.overlay_hide")) {
return hideOverlayRecipe();
}
@@ -67,6 +71,10 @@ public abstract class ShortcutInputHandler {
public static boolean handleMouseClick(ItemStack stackover) {
if (!NEIClientConfig.isLoaded()) {
return false;
}
if (stackover != null) {
final int button = Mouse.getEventButton();
stackover = stackover.copy();
@@ -514,6 +514,10 @@ public class GuiContainerManager {
if (tooltip.size() > 0) tooltip.set(0, tooltip.get(0) + GuiDraw.TOOLTIP_LINESPACE); // add space after 'title'
drawPagedTooltip(font, mousex + 12, mousey - 12, tooltip);
for (IContainerDrawHandler drawHandler : drawHandlers) {
drawHandler.postRenderTooltips(window, mousex, mousey, tooltip);
}
}
private static int tooltipPage;
@@ -1,5 +1,7 @@
package codechicken.nei.guihook;
import java.util.List;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.inventory.Slot;
@@ -30,6 +32,17 @@ public interface IContainerDrawHandler {
*/
public void postRenderObjects(GuiContainer gui, int mousex, int mousey);
/**
* Use this to draw things that should be drawn just after vanilla slot tooltips, on top of buttons and other
* usually always-on-top widgets.
*
* @param gui An instance of the currentscreen
* @param mousex The x position of the mouse in pixels from left
* @param mousey The y position of the mouse in pixels from top
* @param tooltip The contents of the tooltip that has previously been drawn
*/
default void postRenderTooltips(GuiContainer gui, int mousex, int mousey, List<String> tooltip) {}
/**
* Render something over a slot before the item in the slot.
*
@@ -121,6 +121,43 @@ public class BookmarkRecipeId {
return arr;
}
/**
* Ensures the {@link BookmarkRecipeId#recipetype} and {@link BookmarkRecipeId#position} fields point to a valid
* recipe. This is needed, because the recipe IDs are loaded lazily on first use.
*
* @param recipeHandlers The subset of recipe handlers to search for the recipe
*/
public void updateTargetRecipe(List<? extends IRecipeHandler> recipeHandlers) {
if (this.recipetype == -1 || this.position == -1) {
this.recipetype = 0;
this.position = 0;
if (this.handlerName != null) {
for (int j = 0; j < recipeHandlers.size(); j++) {
IRecipeHandler localHandler = recipeHandlers.get(j);
HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(localHandler);
if (localHandlerInfo.getHandlerName().equals(this.handlerName)) {
if (!this.ingredients.isEmpty()) {
for (int i = 0; i < localHandler.numRecipes(); i++) {
if (this.equalsIngredients(localHandler.getIngredientStacks(i))) {
this.recipetype = j;
this.position = i;
break;
}
}
}
break;
}
}
}
}
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
@@ -28,28 +28,27 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
public static boolean openRecipeGui(String outputId, final Boolean overlay, final Boolean shift,
Object... results) {
return createRecipeGui(outputId, overlay, shift, true, results) != null;
}
public static GuiRecipe<?> createRecipeGui(String outputId, final Boolean overlay, final Boolean shift,
final boolean open, Object... results) {
final Minecraft mc = NEIClientUtils.mc();
final BookmarkRecipeId recipeId = "item".equals(outputId)
? getRecipeId(mc.currentScreen, (ItemStack) results[0])
: getCurrentRecipe(mc.currentScreen);
if (overlay && recipeId == null) return false;
if (overlay && recipeId == null) return null;
final RecipeHandlerQuery<ICraftingHandler> recipeQuery = new RecipeHandlerQuery<>(
h -> h.getRecipeHandler(outputId, results),
craftinghandlers,
serialCraftingHandlers,
"Error while looking up crafting recipe",
"outputId: " + outputId,
"results: " + Arrays.toString(results));
final ArrayList<ICraftingHandler> handlers = recipeQuery.runWithProfiling("recipe.concurrent.crafting");
final ArrayList<ICraftingHandler> handlers = getCraftingHandlers(outputId, results);
if (!handlers.isEmpty()) {
GuiCraftingRecipe gui = new GuiCraftingRecipe(handlers, recipeId);
mc.displayGuiScreen(gui);
if (open) {
mc.displayGuiScreen(gui);
}
if (recipeId != null) {
gui.openTargetRecipe(gui.recipeId);
@@ -59,10 +58,22 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
gui.overlayRecipe(gui.recipeId.position, shift);
}
return true;
return gui;
}
return false;
return null;
}
public static ArrayList<ICraftingHandler> getCraftingHandlers(String outputId, Object... results) {
final RecipeHandlerQuery<ICraftingHandler> recipeQuery = new RecipeHandlerQuery<>(
h -> h.getRecipeHandler(outputId, results),
craftinghandlers,
serialCraftingHandlers,
"Error while looking up crafting recipe",
"outputId: " + outputId,
"results: " + Arrays.toString(results));
return recipeQuery.runWithProfiling("recipe.concurrent.crafting");
}
protected static BookmarkRecipeId getRecipeId(GuiScreen gui, ItemStack stackover) {
@@ -1,5 +1,7 @@
package codechicken.nei.recipe;
import static codechicken.lib.gui.GuiDraw.fontRenderer;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
@@ -11,8 +13,10 @@ import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
@@ -59,6 +63,8 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
private static final int buttonWidth = 13;
private static final int buttonHeight = 12;
private boolean limitToOneRecipe = false;
// Background image
final DrawableResource bgTop = new DrawableBuilder("nei:textures/gui/recipebg.png", 0, BG_TOP_Y, 176, BG_TOP_HEIGHT)
.build();
@@ -100,6 +106,12 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
private int yShift = 0;
/**
* This will only be true iff height hacking has been configured for the current recipe handler AND we are currently
* within the scope of an active {@link HeightHack} instance.
*/
private boolean isHeightHackApplied = false;
protected GuiRecipe(GuiScreen prevgui) {
super(new ContainerRecipe());
recipeTabs = new GuiRecipeTabs(this);
@@ -116,9 +128,14 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
}
/** Changes the GUI mode to only display one recipe at a time, e.g. for tooltip usage */
public void limitToOneRecipe() {
limitToOneRecipe = true;
}
/**
* Many old mods assumed a fixed NEI window height of {@code 166} pixels. Now that this is no longer the case, their
* fluid mouseover handling is broken. This helper class fixes these old mods by hacking the {@link #height}
* tooltip and click zone handling is broken. This helper class fixes these old mods by hacking the {@link #height}
* property's value so that these old mods' calculations return the correct value for the new height.
*
* <p>
@@ -128,40 +145,77 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
private class HeightHack implements AutoCloseable {
private final int trueHeight;
private final int trueGuiTop;
private final GuiScreen trueGui;
private HeightHack() {
trueHeight = height;
trueGuiTop = guiTop;
trueGui = Minecraft.getMinecraft().currentScreen;
boolean applyHeightHack = NEIClientConfig.heightHackHandlerRegex.stream()
isHeightHackApplied = NEIClientConfig.heightHackHandlerRegex.stream()
.map(pattern -> pattern.matcher(handler.getHandlerId())).anyMatch(Matcher::matches);
if (applyHeightHack) {
// The old mods use the calculation ((height - 166) / 2) to compute the y-value of
// the top edge of the NEI window.
// guiTop is exactly that: the y-value of the top edge of the NEI window.
// So we set height to be equal to the inverse of this calculation, applied to
// guiTop, so that old mods get the right result back when they use this calculation.
if (isHeightHackApplied) {
// guiTop is the top edge of the recipe screen on the y-axis. Old recipe handlers expect a single paging
// widget at the top and at the bottom of the recipe screen with a height of 16px each, but GTNH NEI
// moved the bottom paging widget to the top following JEI's design, increasing the "forbidden" zone at
// the top to 32px. To fix these old recipe handlers we move guiTop down 16px so that they can keep
// working with the old 16px gap.
guiTop += 16;
// The old NEI recipe screen had a fixed width and height and was always centered on the screen. Legacy
// recipe handlers use the calculation ((height - 166) / 2) to compute the y-value of the top edge of
// the NEI window. GTNH NEI changes the layout so that the recipe screen length now has a flexible
// length and is no longer centered vertically. In order for the top-of-screen calculation to return the
// correct result, we have to hack the height field with an inverse of the calculation using the actual
// top of the recipe screen stored in guiTop.
height = (2 * guiTop) + 166;
// For reference, in case we ever need to modify width as well:
// the legacy calculation used for width is ((width - 176) / 2), which should
// evaluate to be equal to guiWidth (the x-value of the left edge of the NEI window).
// So if we wanted to override width as well, we'd do this:
// the legacy calculation used for width is ((width - 176) / 2), which should evaluate to be equal to
// guiWidth (the x-value of the left edge of the NEI recipe screen). So if we wanted to override width
// as well, we'd do this:
// width = (2 * guiWidth) + 176;
// Recipe handlers may assume the current screen is the GuiRecipe object, which is not the case in
// recipe tooltips drawn on the bookmarks panel with the main inventory open.
if (limitToOneRecipe) {
Minecraft.getMinecraft().currentScreen = GuiRecipe.this;
}
}
}
@Override
public void close() {
guiTop = trueGuiTop;
height = trueHeight;
Minecraft.getMinecraft().currentScreen = trueGui;
isHeightHackApplied = false;
}
}
@Override
@SuppressWarnings("unchecked")
public void initGui() {
initGui(false);
}
@SuppressWarnings("unchecked")
public void initGui(boolean asWidget) {
xSize = 176;
ySize = Math.min(Math.max(height - 68, 166), 370);
super.initGui();
if (!asWidget) {
super.initGui();
} else {
this.guiLeft = (this.width - this.xSize) / 2;
this.mc = Minecraft.getMinecraft();
this.fontRendererObj = mc.fontRenderer;
ScaledResolution scaledresolution = new ScaledResolution(
this.mc,
this.mc.displayWidth,
this.mc.displayHeight);
this.width = scaledresolution.getScaledWidth();
this.height = scaledresolution.getScaledHeight();
}
guiTop = (height - ySize) / 2 + 10;
currenthandlers = getCurrentRecipeHandlers(); // Probably don't comment me out
@@ -198,14 +252,23 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final int recipesPerPage = getRecipesPerPage();
overlayButtons = new GuiButton[recipesPerPage];
final int xOffset = limitToOneRecipe ? ((xSize / 2) - 2) : (width / 2);
final String overlayKeyName = NEIClientConfig
.getKeyName(NEIClientConfig.getKeyBinding("gui.overlay_use"), true);
for (int i = 0; i < recipesPerPage; i++) {
overlayButtons[i] = new GuiNEIButton(
OVERLAY_BUTTON_ID_START + i,
(width / 2) + 65,
guiTop + 16 + (handlerInfo.getHeight() * (i + 1)) - 2,
xOffset + 65,
16 + (handlerInfo.getHeight() * (i + 1)) - 2,
buttonWidth,
buttonHeight,
"?");
if (overlayKeyName != null && limitToOneRecipe) {
overlayButtons[i].visible = true;
overlayButtons[i].displayString = overlayKeyName;
overlayButtons[i].width = fontRenderer.getStringWidth(overlayKeyName) + 6;
overlayButtons[i].xPosition = xOffset + 85 - overlayButtons[i].width;
}
}
Collections.addAll(buttonList, overlayButtons);
itemPresenceCacheRecipe = -1;
@@ -232,41 +295,17 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
initOverlayButtons();
}
public List<GuiButton> getOverlayButtons() {
return Collections.unmodifiableList(Arrays.asList(overlayButtons));
}
public void openTargetRecipe(BookmarkRecipeId recipeId) {
if (recipeId == null) {
return;
}
if (recipeId.recipetype == -1 || recipeId.position == -1) {
recipeId.recipetype = 0;
recipeId.position = 0;
if (recipeId.handlerName != null) {
for (int j = 0; j < currenthandlers.size(); j++) {
H localHandler = currenthandlers.get(j);
HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(localHandler);
if (localHandlerInfo.getHandlerName().equals(recipeId.handlerName)) {
if (!recipeId.ingredients.isEmpty()) {
for (int i = 0; i < localHandler.numRecipes(); i++) {
if (recipeId.equalsIngredients(localHandler.getIngredientStacks(i))) {
recipeId.recipetype = j;
recipeId.position = i;
break;
}
}
}
break;
}
}
}
}
recipeId.updateTargetRecipe(currenthandlers);
setRecipePage(recipeId.recipetype, recipeId.position);
}
@@ -438,6 +477,11 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
@Override
public void updateScreen() {
super.updateScreen();
updateAsTooltip();
}
/** updateScreen() that's safe to call when this isn't the currently open GUI */
public void updateAsTooltip() {
handler.onUpdate();
refreshPage();
}
@@ -454,9 +498,9 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
@Override
public List<String> handleItemTooltip(GuiContainer gui, ItemStack stack, int mousex, int mousey,
List<String> currenttip) {
// Height hacking is probably not needed as it doesn't look like any mods check mouse
// position for this method.
for (int i : getRecipeIndices()) currenttip = handler.handleItemTooltip(this, stack, currenttip, i);
try (HeightHack heightHack = new HeightHack()) {
for (int i : getRecipeIndices()) currenttip = handler.handleItemTooltip(this, stack, currenttip, i);
}
return currenttip;
}
@@ -542,6 +586,12 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final int recipesPerPage = getRecipesPerPage();
for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) {
Point p = getRecipePosition(i);
// Legacy recipe handlers only expect a single paging widget at the top of the recipe screen, in contrast,
// GTNH NEI moves the recipe paging widget from the bottom to the top, which means said legacy handlers will
// position item slots 16px too high in the screen.
if (isHeightHackApplied) {
p.translate(0, 16);
}
List<PositionedStack> stacks = handler.getIngredientStacks(i);
for (PositionedStack stack : stacks) slotcontainer.addSlot(stack, p.x, p.y);
@@ -552,11 +602,13 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
PositionedStack result = handler.getResultStack(i);
if (result != null) slotcontainer.addSlot(result, p.x, p.y);
List<PositionedStack> catalysts = RecipeCatalysts.getRecipeCatalysts(handler);
for (PositionedStack catalyst : catalysts) {
int xOffset = -GuiRecipeCatalyst.ingredientSize + 1;
int yOffset = BG_TOP_Y + GuiRecipeCatalyst.fullBorder;
slotcontainer.addSlot(catalyst, xOffset, yOffset);
if (!limitToOneRecipe) {
List<PositionedStack> catalysts = RecipeCatalysts.getRecipeCatalysts(handler);
for (PositionedStack catalyst : catalysts) {
int xOffset = -GuiRecipeCatalyst.ingredientSize + 1;
int yOffset = BG_TOP_Y + GuiRecipeCatalyst.fullBorder;
slotcontainer.addSlot(catalyst, xOffset, yOffset);
}
}
}
}
@@ -590,21 +642,24 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
public void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
GuiContainerManager.enable2DRender();
final int recipesPerPage = getRecipesPerPage();
String s = handler.getRecipeName().trim();
fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 5, 0xffffff);
s = NEIClientUtils.translate("recipe.page", page + 1, (handler.numRecipes() - 1) / recipesPerPage + 1);
fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 19, 0xffffff);
final int ySkip = limitToOneRecipe ? 25 : 0;
if (!limitToOneRecipe) {
String s = handler.getRecipeName().trim();
fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 5, 0xffffff);
s = NEIClientUtils.translate("recipe.page", page + 1, (handler.numRecipes() - 1) / recipesPerPage + 1);
fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 19, 0xffffff);
}
final boolean drawItemPresence = NEIClientConfig.isJEIStyleItemPresenceOverlayVisible();
GL11.glPushMatrix();
GL11.glTranslatef(5, 32 + yShift, 0);
GL11.glTranslatef(5, 32 - ySkip + yShift, 0);
try (HeightHack heightHack = new HeightHack()) {
for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) {
handler.drawForeground(i);
if (drawItemPresence && isMouseOverOverlayButton(i - page * recipesPerPage)
if (drawItemPresence && (isMouseOverOverlayButton(i - page * recipesPerPage) || limitToOneRecipe)
&& firstGui.inventorySlots != null) {
List<PositionedStack> ingredients = handler.getIngredientStacks(i);
if (itemPresenceCacheRecipe != i || itemPresenceCacheSlots == null
@@ -624,28 +679,27 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
@Override
protected void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY) {
public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY) {
final int recipesPerPage = getRecipesPerPage();
final int ySkip = limitToOneRecipe ? 25 : 0;
GL11.glColor4f(1, 1, 1, 1);
final int j = (width - xSize) / 2;
final int k = guiTop;
drawBackgroundTiled(j, k);
drawBackgroundTiled(guiLeft, guiTop);
drawRect(
guiLeft + borderPadding + buttonWidth - 1,
nexttype.yPosition,
guiLeft + xSize - borderPadding - buttonWidth,
nexttype.yPosition + buttonHeight,
0x30000000);
drawRect(
guiLeft + borderPadding + buttonWidth - 1,
nextpage.yPosition,
guiLeft + xSize - borderPadding - buttonWidth,
nextpage.yPosition + buttonHeight,
0x30000000);
if (NEIClientConfig.areJEIStyleTabsVisible() && !limitToOneRecipe) {
drawRect(
guiLeft + borderPadding + buttonWidth - 1,
nexttype.yPosition,
guiLeft + xSize - borderPadding - buttonWidth,
nexttype.yPosition + buttonHeight,
0x30000000);
drawRect(
guiLeft + borderPadding + buttonWidth - 1,
nextpage.yPosition,
guiLeft + xSize - borderPadding - buttonWidth,
nextpage.yPosition + buttonHeight,
0x30000000);
if (NEIClientConfig.areJEIStyleTabsVisible()) {
RenderHelper.enableGUIStandardItemLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
recipeTabs.draw(mouseX, mouseY);
@@ -656,7 +710,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
GL11.glPushMatrix();
GL11.glTranslatef(j + 5, k + 32 + yShift, 0);
GL11.glTranslatef(guiLeft + 5, guiTop - ySkip + 32 + yShift, 0);
try (HeightHack heightHack = new HeightHack()) {
for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) {
handler.drawBackground(i);
@@ -668,10 +722,12 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
private void drawBackgroundTiled(int j, int k) {
// Draw the top
final int ySkip = limitToOneRecipe ? 25 : 0;
final int handlerHeight = limitToOneRecipe ? getHeightAsWidget() : ySize;
bgTop.draw(j, k + BG_TOP_Y);
// Maybe Tile the middle
final int tiledHeight = ySize - BG_TOP_HEIGHT - BG_BOTTOM_HEIGHT;
final int tiledHeight = handlerHeight - ySkip - BG_TOP_HEIGHT - BG_BOTTOM_HEIGHT;
if (tiledHeight > 0) {
final int yTileCount = tiledHeight / BG_MIDDLE_HEIGHT;
@@ -688,7 +744,11 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
// Draw the bottom
bgBottom.draw(j, k + ySize - BG_BOTTOM_HEIGHT);
bgBottom.draw(j, k + handlerHeight - ySkip - BG_BOTTOM_HEIGHT);
}
public int getHeightAsWidget() {
return (handlerInfo == null) ? ySize : (handlerInfo.getHeight() + 56);
}
@Override
@@ -724,7 +784,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
private int getRecipesPerPage() {
return getRecipesPerPage(handlerInfo);
return limitToOneRecipe ? 1 : getRecipesPerPage(handlerInfo);
}
private int getRecipesPerPage(HandlerInfo handlerInfo) {
@@ -735,7 +795,14 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
public Point getRecipePosition(int recipe) {
return new Point(5, 32 + yShift + ((recipe % getRecipesPerPage()) * handlerInfo.getHeight()));
// Legacy recipe handlers using the height hack might use getRecipePosition in combination with guiTop/height to
// position certain elements like tooltips. Since guiTop is moved down by 16px during height hacking, we need to
// reduce the vertical shift here to 16px instead of 32px.
return new Point(
5,
(isHeightHackApplied ? 16 : 32) - (limitToOneRecipe ? 25 : 0)
+ yShift
+ ((recipe % getRecipesPerPage()) * handlerInfo.getHeight()));
}
public abstract ArrayList<H> getCurrentRecipeHandlers();
@@ -0,0 +1,15 @@
# Each line in this file should either be a comment (line starts with '#'), or a fully-qualified class name (that
# includes the package name) for a recipe handler. Wildcard patterns ('*') can be used to catch multiple classes with a
# single entry.
# If you delete this file, it will be regenerated with the default height hacked handlers list.
buildcraft.compat.nei.*
cofh.thermalexpansion.plugins.nei.handlers.*
crazypants.enderio.nei.*
forestry.factory.recipes.nei.*
ic2.neiIntegration.core.recipehandler.*
mariculture.plugins.nei.*
redgear.brewcraft.plugins.nei.*
tconstruct.plugins.nei.*
WayofTime.alchemicalWizardry.client.nei.*
exnihilo.compatibility.nei.*
blusunrize.immersiveengineering.client.nei.*
@@ -0,0 +1,6 @@
# This version of NEI loads recipes in parallel. Some recipe handlers have not been designed with that in mind and might
# crash. List these handlers in this file to force them to be loaded single threaded. Each line in this file should
# either be a comment (line starts with '#'), or a fully-qualified class name (that includes the package name) for a
# recipe handler.
# If you delete this file, it will be regenerated with the default serial handlers list.
WayofTime.alchemicalWizardry.client.nei.NEIAlchemyRecipeHandler
@@ -373,11 +373,11 @@ NEIArcFurnaceHandlerCarrot Ingot,Immersive Engineering,minecraft:carrot,,Immersi
NEIArcFurnaceHandlerExtraction,Immersive Engineering,minecraft:glowstone_dust,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIShaderBagHandler,Immersive Engineering,ImmersiveEngineering:shaderBag,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIBlueprintHandler,Immersive Engineering,ImmersiveEngineering:woodenDevice:5,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEICokeOvenHandler,Immersive Engineering,ImmersiveEngineering:stoneDecoration:1,,ImmersiveEngineering,TRUE,,,71,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIBlastFurnaceHandler,Immersive Engineering,ImmersiveEngineering:stoneDecoration:2,,ImmersiveEngineering,TRUE,,,71,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEISqueezerHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:2,,ImmersiveEngineering,TRUE,,,71,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIFermenterHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:3,,ImmersiveEngineering,TRUE,,,71,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIRefineryHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:4,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEICokeOvenHandler,Immersive Engineering,ImmersiveEngineering:stoneDecoration:1,,ImmersiveEngineering,TRUE,,,71,166,2,,,,,,
blusunrize.immersiveengineering.client.nei.NEIBlastFurnaceHandler,Immersive Engineering,ImmersiveEngineering:stoneDecoration:2,,ImmersiveEngineering,TRUE,,,71,166,2,,,,,,
blusunrize.immersiveengineering.client.nei.NEISqueezerHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:2,,ImmersiveEngineering,TRUE,,1,64,166,2,,,,,,
blusunrize.immersiveengineering.client.nei.NEIFermenterHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:3,,ImmersiveEngineering,TRUE,,1,64,166,2,,,,,,
blusunrize.immersiveengineering.client.nei.NEIRefineryHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:4,,ImmersiveEngineering,TRUE,,,65,166,2,,,,,,
blusunrize.immersiveengineering.client.nei.NEIBottlingMachineHandler,Immersive Engineering,minecraft:glass_bottle,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEIMetalPressHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:13,,ImmersiveEngineering,TRUE,,,65,166,5,,,,,,
blusunrize.immersiveengineering.client.nei.NEICrusherHandler,Immersive Engineering,ImmersiveEngineering:metalMultiblock:5,,ImmersiveEngineering,TRUE,,,71,166,5,,,,,,
@@ -410,13 +410,13 @@ cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCharger,Thermal Expansio
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingMachine,Thermal Expansion,ThermalExpansion:Frame,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingSecure,Thermal Expansion,ThermalExpansion:material:16,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingUpgrade,Thermal Expansion,ThermalExpansion:Frame:3,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCrucible,Thermal Expansion,ThermalExpansion:Machine:4,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCrucible,Thermal Expansion,ThermalExpansion:Machine:4,,ThermalExpansion,TRUE,,,65,166,2,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerFurnace,Thermal Expansion,ThermalExpansion:Machine:0,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerInsolator,Thermal Expansion,ThermalExpansion:Machine:11,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerInsolator,Thermal Expansion,ThermalExpansion:Machine:11,,ThermalExpansion,TRUE,,,65,166,2,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerPulverizer,Thermal Expansion,ThermalExpansion:Machine:1,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerSawmill,Thermal Expansion,ThermalExpansion:Machine:2,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerSmelter,Thermal Expansion,ThermalExpansion:Machine:3,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerTransposer,Thermal Expansion,ThermalExpansion:Machine:5,,ThermalExpansion,TRUE,,,65,166,5,,,,,,
cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerTransposer,Thermal Expansion,ThermalExpansion:Machine:5,,ThermalExpansion,TRUE,,,65,166,2,,,,,,
mekanism.client.nei.ChemicalCrystallizerRecipeHandler,Mekanism,Mekanism:MachineBlock2:8,,Mekanism,TRUE,,,79,,4,,,,,,
mekanism.client.nei.ChemicalDissolutionChamberRecipeHandler,Mekanism,Mekanism:MachineBlock2:6,,Mekanism,TRUE,,,79,,4,,,,,,
mekanism.client.nei.ChemicalInfuserRecipeHandler,Mekanism,Mekanism:MachineBlock2:2,,Mekanism,TRUE,,,80,,4,,,,,,
@@ -437,7 +437,7 @@ mekanism.client.nei.ShapelessMekanismRecipeHandler,Mekanism,minecraft:crafting_t
mekanism.client.nei.SolarNeutronRecipeHandler,Mekanism,Mekanism:MachineBlock3:1,,Mekanism,TRUE,,,70,,5,,,,,,
mekanism.client.nei.ThermalEvaporationRecipeHandler,Mekanism,Mekanism:BasicBlock:14,,Mekanism,TRUE,,,62,,5,,,,,,
exnihilo.compatibility.nei.RecipeHandlerHammer,Ex Nihilo,exnihilo:hammer_iron,,exnihilo,TRUE,,,58,,5,,,,,,
exnihilo.compatibility.nei.RecipeHandlerSieve,Ex Nihilo,exnihilo:sifting_table,,exnihilo,TRUE,,,130,,2,,,,,,
exnihilo.compatibility.nei.RecipeHandlerSieve,Ex Nihilo,exnihilo:sifting_table,,exnihilo,TRUE,,,130,,1,,,,,,
gregtech.Alloy,GregTech-Addon,gregtech_addon:machine:56,,gregtech_addon,TRUE,,15,145,166,2,,,,,,
gregtech.Assembler,GregTech-Addon,gregtech_addon:machine:60,,gregtech_addon,TRUE,,15,145,166,2,,,,,,
gregtech.Bender,GregTech-Addon,gregtech_addon:machine:59,,gregtech_addon,TRUE,,15,145,166,2,,,,,,
1 handler modName itemName nbtInfo modId modRequired excludedModId yShift handlerHeight handlerWidth maxRecipesPerPage imageResource imageX imageY imageWidth imageHeight itemNotes
373 NEIArcFurnaceHandlerExtraction Immersive Engineering minecraft:glowstone_dust ImmersiveEngineering TRUE 65 166 5
374 blusunrize.immersiveengineering.client.nei.NEIShaderBagHandler Immersive Engineering ImmersiveEngineering:shaderBag ImmersiveEngineering TRUE 65 166 5
375 blusunrize.immersiveengineering.client.nei.NEIBlueprintHandler Immersive Engineering ImmersiveEngineering:woodenDevice:5 ImmersiveEngineering TRUE 65 166 5
376 blusunrize.immersiveengineering.client.nei.NEICokeOvenHandler Immersive Engineering ImmersiveEngineering:stoneDecoration:1 ImmersiveEngineering TRUE 71 166 5 2
377 blusunrize.immersiveengineering.client.nei.NEIBlastFurnaceHandler Immersive Engineering ImmersiveEngineering:stoneDecoration:2 ImmersiveEngineering TRUE 71 166 5 2
378 blusunrize.immersiveengineering.client.nei.NEISqueezerHandler Immersive Engineering ImmersiveEngineering:metalMultiblock:2 ImmersiveEngineering TRUE 1 71 64 166 5 2
379 blusunrize.immersiveengineering.client.nei.NEIFermenterHandler Immersive Engineering ImmersiveEngineering:metalMultiblock:3 ImmersiveEngineering TRUE 1 71 64 166 5 2
380 blusunrize.immersiveengineering.client.nei.NEIRefineryHandler Immersive Engineering ImmersiveEngineering:metalMultiblock:4 ImmersiveEngineering TRUE 65 166 5 2
381 blusunrize.immersiveengineering.client.nei.NEIBottlingMachineHandler Immersive Engineering minecraft:glass_bottle ImmersiveEngineering TRUE 65 166 5
382 blusunrize.immersiveengineering.client.nei.NEIMetalPressHandler Immersive Engineering ImmersiveEngineering:metalMultiblock:13 ImmersiveEngineering TRUE 65 166 5
383 blusunrize.immersiveengineering.client.nei.NEICrusherHandler Immersive Engineering ImmersiveEngineering:metalMultiblock:5 ImmersiveEngineering TRUE 71 166 5
410 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingMachine Thermal Expansion ThermalExpansion:Frame ThermalExpansion TRUE 65 166 5
411 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingSecure Thermal Expansion ThermalExpansion:material:16 ThermalExpansion TRUE 65 166 5
412 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCraftingUpgrade Thermal Expansion ThermalExpansion:Frame:3 ThermalExpansion TRUE 65 166 5
413 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerCrucible Thermal Expansion ThermalExpansion:Machine:4 ThermalExpansion TRUE 65 166 5 2
414 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerFurnace Thermal Expansion ThermalExpansion:Machine:0 ThermalExpansion TRUE 65 166 5
415 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerInsolator Thermal Expansion ThermalExpansion:Machine:11 ThermalExpansion TRUE 65 166 5 2
416 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerPulverizer Thermal Expansion ThermalExpansion:Machine:1 ThermalExpansion TRUE 65 166 5
417 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerSawmill Thermal Expansion ThermalExpansion:Machine:2 ThermalExpansion TRUE 65 166 5
418 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerSmelter Thermal Expansion ThermalExpansion:Machine:3 ThermalExpansion TRUE 65 166 5
419 cofh.thermalexpansion.plugins.nei.handlers.RecipeHandlerTransposer Thermal Expansion ThermalExpansion:Machine:5 ThermalExpansion TRUE 65 166 5 2
420 mekanism.client.nei.ChemicalCrystallizerRecipeHandler Mekanism Mekanism:MachineBlock2:8 Mekanism TRUE 79 4
421 mekanism.client.nei.ChemicalDissolutionChamberRecipeHandler Mekanism Mekanism:MachineBlock2:6 Mekanism TRUE 79 4
422 mekanism.client.nei.ChemicalInfuserRecipeHandler Mekanism Mekanism:MachineBlock2:2 Mekanism TRUE 80 4
437 mekanism.client.nei.SolarNeutronRecipeHandler Mekanism Mekanism:MachineBlock3:1 Mekanism TRUE 70 5
438 mekanism.client.nei.ThermalEvaporationRecipeHandler Mekanism Mekanism:BasicBlock:14 Mekanism TRUE 62 5
439 exnihilo.compatibility.nei.RecipeHandlerHammer Ex Nihilo exnihilo:hammer_iron exnihilo TRUE 58 5
440 exnihilo.compatibility.nei.RecipeHandlerSieve Ex Nihilo exnihilo:sifting_table exnihilo TRUE 130 2 1
441 gregtech.Alloy GregTech-Addon gregtech_addon:machine:56 gregtech_addon TRUE 15 145 166 2
442 gregtech.Assembler GregTech-Addon gregtech_addon:machine:60 gregtech_addon TRUE 15 145 166 2
443 gregtech.Bender GregTech-Addon gregtech_addon:machine:59 gregtech_addon TRUE 15 145 166 2
@@ -152,6 +152,9 @@ nei.options.inventory.saveCurrentRecipeInBookmarksEnabled.false=Disabled
nei.options.inventory.useNBTInBookmarks=Use NBT in Bookmarks
nei.options.inventory.useNBTInBookmarks.true=Yes
nei.options.inventory.useNBTInBookmarks.false=No
nei.options.inventory.recipeTooltipsEnabled=Show recipe tooltips in Bookmarks
nei.options.inventory.recipeTooltipsEnabled.true=Yes
nei.options.inventory.recipeTooltipsEnabled.false=No
nei.options.inventory.bookmarksAnimationEnabled=REI Style Animation in Bookmarks
nei.options.inventory.bookmarksAnimationEnabled.true=Yes
nei.options.inventory.bookmarksAnimationEnabled.false=No
@@ -342,4 +345,4 @@ nei.presets.whitelist.tooltip=Whitelist
nei.presets.textfield.tooltip.1=Enter - save
nei.presets.textfield.tooltip.2=Escape - cancel
nei.presets.label.tooltip=Double click to edit
nei.presets.label.selected=%s Presets
nei.presets.label.selected=%s Presets