diff --git a/src/main/java/codechicken/nei/CompositeTooltipLineHandler.java b/src/main/java/codechicken/nei/CompositeTooltipLineHandler.java new file mode 100644 index 00000000..b10202ae --- /dev/null +++ b/src/main/java/codechicken/nei/CompositeTooltipLineHandler.java @@ -0,0 +1,101 @@ +package codechicken.nei; + +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.gui.GuiDraw.ITooltipLineHandler; + +public abstract class CompositeTooltipLineHandler implements ITooltipLineHandler { + + protected static final int SCREEN_MARGIN = 8 * 2 + 10; + protected static final int RESERVED_HEIGHT = 40; + + protected final List lines = new ArrayList<>(); + protected final Dimension size = new Dimension(); + + protected boolean created = false; + protected int screenHeight = 0; + + protected abstract void createLines(); + + protected boolean needsUpdate() { + return false; + } + + @Override + public Dimension getSize() { + ensureCreated(); + return this.size; + } + + @Override + public void draw(int x, int y) { + if (this.size.height == 0) return; + + for (ItemsTooltipLineHandler line : this.lines) { + line.draw(x, y); + y += line.getSize().height; + } + } + + protected void ensureCreated() { + boolean update = needsUpdate(); + update = this.screenHeight != (this.screenHeight = GuiDraw.displaySize().height) || update; + + if (this.created && !update) { + return; + } + + this.created = true; + this.lines.clear(); + this.size.setSize(0, 0); + + createLines(); + fitToScreen(); + + for (ItemsTooltipLineHandler line : this.lines) { + this.size.width = Math.max(this.size.width, line.getSize().width); + this.size.height += line.getSize().height; + } + } + + protected void fitToScreen() { + if (this.lines.isEmpty()) { + return; + } + + final List ordered = new ArrayList<>(this.lines); + ordered.sort(Comparator.comparingInt(ItemsTooltipLineHandler::getRows)); + + int budget = availableHeight() / ItemsTooltipLineHandler.SLOT_SIZE; + int rest = ordered.size(); + + for (ItemsTooltipLineHandler line : ordered) { + final int fair = Math.max(1, budget / rest); + + if (line.getRows() > fair) { + line.setMaxRows(fair); + } + + budget -= line.getRows(); + rest--; + } + } + + protected int availableHeight() { + return GuiDraw.displaySize().height - SCREEN_MARGIN + - RESERVED_HEIGHT + - this.lines.size() * ItemsTooltipLineHandler.headerHeight(); + } + + protected static int maxLineRows() { + final int height = GuiDraw.displaySize().height - SCREEN_MARGIN + - RESERVED_HEIGHT + - ItemsTooltipLineHandler.headerHeight(); + return Math.max(1, height / ItemsTooltipLineHandler.SLOT_SIZE); + } + +} diff --git a/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java b/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java deleted file mode 100644 index 4aad640e..00000000 --- a/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java +++ /dev/null @@ -1,125 +0,0 @@ -package codechicken.nei; - -import static codechicken.lib.gui.GuiDraw.fontRenderer; - -import java.util.ArrayList; -import java.util.List; - -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumChatFormatting; - -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; - -import codechicken.lib.gui.GuiDraw; -import codechicken.nei.guihook.GuiContainerManager; - -public class ItemsListTooltipLineHandler extends ItemsTooltipLineHandler { - - protected static final int NAME_MARGIN = 2; - - protected final List names = new ArrayList<>(); - protected int tailCount = 0; - - public ItemsListTooltipLineHandler(String label, List items, int maxRows) { - super(label, items, true, maxRows); - - this.rows = Math.min(maxRows, this.length); - this.count = this.length > this.rows ? this.rows - 1 : this.length; - - int width = fontRenderer.getStringWidth(this.label) + 15; - - for (int index = 0; index < this.count; index++) { - final String name = this.items.get(index).getDisplayName(); - this.names.add(name); - width = Math.max(width, SLOT_SIZE + NAME_MARGIN + fontRenderer.getStringWidth(name)); - } - - if (this.count < this.length) { - final int tail = this.length - this.count; - width = Math.max(width, Math.min(tail, MAX_COLUMNS) * SLOT_SIZE); - - final int fit = width / SLOT_SIZE; - - if (tail <= fit) { - this.tailCount = tail; - } else { - final String text = "+" + (tail - fit); - this.tailCount = Math - .max(0, fit - (int) Math.ceil((float) (fontRenderer.getStringWidth(text) - 2) / SLOT_SIZE)); - } - } - - this.size.width = this.length > 0 ? width : 0; - this.size.height = this.length > 0 ? this.rows * SLOT_SIZE + fontRenderer.FONT_HEIGHT + 2 + MARGIN_TOP : 0; - } - - @Override - public void setLabel(String label) { - this.label = label; - this.size.width = Math.max(this.size.width, fontRenderer.getStringWidth(this.label) + 15); - } - - @Override - public void draw(int x, int y) { - if (this.length == 0) return; - - y += MARGIN_TOP; - - fontRenderer.drawStringWithShadow(this.labelColor + this.label + ":", x, y, 0); - - GL11.glEnable(GL12.GL_RESCALE_NORMAL); - - GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT); - RenderHelper.enableGUIStandardItemLighting(); - - final int xTranslation = x; - final int yTranslation = y + fontRenderer.FONT_HEIGHT + 2; - final int zTranslation = GuiContainerManager.TOOLTIP_Z_OFFSET; - GL11.glTranslatef(xTranslation, yTranslation, zTranslation); - - for (int index = 0; index < this.count; index++) { - drawSlot(0, index * SLOT_SIZE, index); - } - - for (int index = 0; index < this.tailCount; index++) { - drawSlot(index * SLOT_SIZE, this.count * SLOT_SIZE, this.count + index); - } - - NEIClientUtils.gl2DRenderContext(() -> { - final int textShift = ICON_OFFSET + Math.round((ICON_SIZE - fontRenderer.FONT_HEIGHT) / 2f); - - for (int index = 0; index < this.count; index++) { - fontRenderer.drawStringWithShadow( - EnumChatFormatting.GRAY + this.names.get(index), - SLOT_SIZE + NAME_MARGIN, - index * SLOT_SIZE + textShift, - 0xFFFFFF); - } - - final int hidden = this.length - this.count - this.tailCount; - - if (hidden > 0) { - final String text = "+" + hidden; - fontRenderer.drawStringWithShadow( - EnumChatFormatting.GRAY + text, - this.size.width - fontRenderer.getStringWidth(text) - 2, - this.count * SLOT_SIZE + textShift, - 0xFFFFFF); - } - }); - - GL11.glTranslatef(-xTranslation, -yTranslation, -zTranslation); - GL11.glPopAttrib(); - } - - protected void drawSlot(int x, int y, int index) { - if (this.activeStackIndex == index) { - NEIClientUtils.gl2DRenderContext(() -> GuiDraw.drawRect(x, y, SLOT_SIZE, SLOT_SIZE, 0x66555555)); - } - - drawStackWithAmount(x, y, index); - } - -} diff --git a/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java b/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java index 1471bde8..9b6575ab 100644 --- a/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java @@ -150,10 +150,14 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { protected static final int ICON_OFFSET = 1; protected static final int MAX_COLUMNS = 11; protected static final int MARGIN_TOP = 2; + protected static final int LABEL_MARGIN = 15; + protected static final int NAME_MARGIN = 2; + protected static final int DEFAULT_MAX_ROWS = 5; protected static final int FLUID_STACK_SIZE = 144; protected final List items; protected final List amounts = new ArrayList<>(); + protected final List names = new ArrayList<>(); protected AmountRenderer amountRenderer; @@ -163,40 +167,115 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { protected int activeStackIndex = -1; protected Dimension size = new Dimension(); - protected int columns = 0; - protected int count = 0; - protected int rows = 0; + protected final boolean showNames; + protected int length = 0; + protected int rows = 0; + protected int nameRows = 0; + protected int gridColumns = 0; + protected int gridCount = 0; + + public static ItemsTooltipLineHandler grid(String label, List items, int maxRows) { + return new ItemsTooltipLineHandler(label, items, true, maxRows, false); + } + + public static ItemsTooltipLineHandler list(String label, List items, int maxRows) { + return new ItemsTooltipLineHandler(label, items, true, maxRows, true); + } public ItemsTooltipLineHandler(String label, List items) { - this(label, items, true, 5); + this(label, items, true, DEFAULT_MAX_ROWS, false); } public ItemsTooltipLineHandler(String label, List items, boolean saveStackSize, int maxRows) { + this(label, items, saveStackSize, maxRows, false); + } + + protected ItemsTooltipLineHandler(String label, List items, boolean saveStackSize, int maxRows, + boolean showNames) { this.label = label; this.items = groupingItemStacks(items); this.amountRenderer = saveStackSize ? new TotalAmountRenderer() : new NoAmountRenderer(); this.length = this.items.size(); + this.showNames = showNames; - if (this.length > 0) { - this.columns = Math.min(MAX_COLUMNS, this.length); - this.rows = Math.min(maxRows, (int) Math.ceil((float) this.length / this.columns)); + setMaxRows(maxRows); + } - this.size.width = Math.max(this.columns * SLOT_SIZE, fontRenderer.getStringWidth(this.label) + 15); - this.size.height = this.rows * SLOT_SIZE + fontRenderer.FONT_HEIGHT + 2 + MARGIN_TOP; + protected static int headerHeight() { + return fontRenderer.FONT_HEIGHT + 2 + MARGIN_TOP; + } - this.count = Math.min( - this.length, - Math.min( - this.columns * this.rows, - this.length > MAX_COLUMNS * maxRows ? (MAX_COLUMNS * maxRows) : Integer.MAX_VALUE)); + public ItemsTooltipLineHandler setMaxRows(int maxRows) { + if (this.length == 0) { + return this; + } - if (this.items.size() > this.count) { - String text = "+" + (this.items.size() - this.count); - this.count -= (int) Math.ceil((float) (fontRenderer.getStringWidth(text) - 2) / SLOT_SIZE); + this.names.clear(); + this.nameRows = 0; + + if (!this.showNames || !layoutWithNames(maxRows)) { + layoutAsGrid(maxRows); + } + + this.size.height = this.rows * SLOT_SIZE + headerHeight(); + + return this; + } + + public int getRows() { + return this.rows; + } + + protected boolean layoutWithNames(int maxRows) { + final int maxNames = this.length <= maxRows ? this.length : maxRows - 1; + final int[] lineWidth = new int[maxNames + 1]; + lineWidth[0] = labelWidth(); + + for (int index = 0; index < maxNames; index++) { + final String name = this.items.get(index).getDisplayName(); + this.names.add(name); + lineWidth[index + 1] = Math + .max(lineWidth[index], SLOT_SIZE + NAME_MARGIN + fontRenderer.getStringWidth(name)); + } + + for (int named = maxNames; named >= 0; named--) { + final int rest = this.length - named; + final int width = Math.max(lineWidth[named], Math.min(rest, MAX_COLUMNS) * SLOT_SIZE); + final int columns = Math.max(1, width / SLOT_SIZE); + final int restRows = (int) Math.ceil((float) rest / columns); + + if (named + restRows <= maxRows) { + this.nameRows = named; + this.gridColumns = columns; + this.gridCount = rest; + this.rows = named + restRows; + this.size.width = width; + return true; } } + this.names.clear(); + return false; + } + + protected void layoutAsGrid(int maxRows) { + this.gridColumns = Math.min(MAX_COLUMNS, this.length); + this.rows = Math.min(maxRows, (int) Math.ceil((float) this.length / this.gridColumns)); + this.gridCount = Math.min(this.length, this.gridColumns * this.rows); + this.size.width = Math.max(this.gridColumns * SLOT_SIZE, labelWidth()); + + if (this.gridCount < this.length) { + this.gridCount -= reservedColumns(this.length - this.gridCount); + } + } + + protected int labelWidth() { + return fontRenderer.getStringWidth(this.label) + LABEL_MARGIN; + } + + protected int reservedColumns(int hidden) { + return (int) Math.ceil((float) (fontRenderer.getStringWidth("+" + hidden) - 2) / SLOT_SIZE); } public ItemsTooltipLineHandler setAmountRenderer(AmountRenderer amountRenderer) { @@ -233,7 +312,7 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { public void setLabel(String label) { this.label = label; - this.size.width = Math.max(this.columns * SLOT_SIZE, fontRenderer.getStringWidth(this.label) + 15); + this.size.width = Math.max(this.size.width, labelWidth()); } @Override @@ -254,40 +333,62 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { final int zTranslation = GuiContainerManager.TOOLTIP_Z_OFFSET; GL11.glTranslatef(xTranslation, yTranslation, zTranslation); - int indexShift = 0; + final int gridShift = gridIndexShift(); - if (this.activeStackIndex != -1) { - indexShift = Math.max(0, Math.min(this.items.size() - this.count, this.activeStackIndex - this.count + 2)); + for (int index = 0; index < this.nameRows; index++) { + drawSlot(0, index * SLOT_SIZE, index); } - for (int index = 0; index < this.count && index + indexShift < this.items.size(); index++) { - int col = index % this.columns; - int row = index / this.columns; + for (int index = 0; index < this.gridCount; index++) { + drawSlot( + (index % this.gridColumns) * SLOT_SIZE, + (this.nameRows + index / this.gridColumns) * SLOT_SIZE, + this.nameRows + gridShift + index); + } - if (this.activeStackIndex == index + indexShift) { - NEIClientUtils.gl2DRenderContext( - () -> GuiDraw.drawRect(col * SLOT_SIZE, row * SLOT_SIZE, SLOT_SIZE, SLOT_SIZE, 0x66555555)); + NEIClientUtils.gl2DRenderContext(() -> { + final int textShift = ICON_OFFSET + Math.round((ICON_SIZE - fontRenderer.FONT_HEIGHT) / 2f); + + for (int index = 0; index < this.nameRows; index++) { + fontRenderer.drawStringWithShadow( + EnumChatFormatting.GRAY + this.names.get(index), + SLOT_SIZE + NAME_MARGIN, + index * SLOT_SIZE + textShift, + 0xFFFFFF); } - drawStackWithAmount(col * SLOT_SIZE, row * SLOT_SIZE, index + indexShift); - } + final int hidden = this.length - this.nameRows - this.gridCount; - if (this.count < this.items.size()) { - final String text = "+" + (this.items.size() - this.count); - - NEIClientUtils.gl2DRenderContext(() -> { + if (hidden > 0) { + final String text = "+" + hidden; fontRenderer.drawStringWithShadow( - text, - MAX_COLUMNS * SLOT_SIZE - fontRenderer.getStringWidth(text) - 2, - (this.rows - 1) * SLOT_SIZE + (SLOT_SIZE - fontRenderer.FONT_HEIGHT) / 2, - 0xee555555); - }); - } + EnumChatFormatting.GRAY + text, + this.size.width - fontRenderer.getStringWidth(text) - 2, + (this.rows - 1) * SLOT_SIZE + textShift, + 0xFFFFFF); + } + }); GL11.glTranslatef(-xTranslation, -yTranslation, -zTranslation); GL11.glPopAttrib(); } + protected int gridIndexShift() { + if (this.nameRows > 0 || this.activeStackIndex == -1) { + return 0; + } + + return Math.max(0, Math.min(this.length - this.gridCount, this.activeStackIndex - this.gridCount + 2)); + } + + protected void drawSlot(int x, int y, int index) { + if (this.activeStackIndex == index) { + NEIClientUtils.gl2DRenderContext(() -> GuiDraw.drawRect(x, y, SLOT_SIZE, SLOT_SIZE, 0x66555555)); + } + + drawStackWithAmount(x, y, index); + } + protected void drawStackWithAmount(int x, int y, int index) { this.amountRenderer.draw(x + ICON_OFFSET, y + ICON_OFFSET, this.items.get(index), this.amounts.get(index)); } diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 7f1a3ed2..88b98899 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -212,9 +212,6 @@ public class NEIClientConfig { .getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.bookmarks.showRecipeHandlerIcon", true)); - tag.getTag("inventory.bookmarks.recipeChainDir").getIntValue(1); - API.addOption(new OptionCycled("inventory.bookmarks.recipeChainDir", 2, true)); - tag.getTag("inventory.bookmarks.ignorePotionOverlap").setComment("Ignore overlap with potion effect HUD") .getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.bookmarks.ignorePotionOverlap", true)); @@ -1153,10 +1150,6 @@ public class NEIClientConfig { return getBooleanSetting("inventory.bookmarks.ignorePotionOverlap"); } - public static int recipeChainDir() { - return getIntSetting("inventory.bookmarks.recipeChainDir"); - } - public static boolean optimizeGuiOverlapComputation() { return getBooleanSetting("inventory.optimize_gui_overlap_computation"); } diff --git a/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java b/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java index 7b9f158b..f4a95710 100644 --- a/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java +++ b/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java @@ -13,7 +13,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.stream.Stream; import javax.imageio.ImageIO; @@ -116,7 +115,7 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu private static final int PADDING = 9; private static final int PADDING_TOP = 22; - private static LRUCache handlerTitleCache = new LRUCache<>(100); + private final LRUCache handlerTitleCache = new LRUCache<>(100); private GuiScreen prevGui; private boolean isFirstScreen = true; @@ -919,15 +918,8 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu super.keyTyped(typedChar, keyCode); } - private static String getHandlerTitle(String handlerName) { - return handlerTitleCache.computeIfAbsent( - handlerName, - hname -> Stream - .concat( - GuiCraftingRecipe.craftinghandlers.stream(), - GuiCraftingRecipe.serialCraftingHandlers.stream()) - .filter(handler -> hname.equals(GuiRecipeTab.getHandlerInfo(handler).getHandlerName())) - .findFirst().map(handler -> handler.getRecipeName().trim()).orElse(hname)); + private String getHandlerTitle(String handlerName) { + return this.handlerTitleCache.computeIfAbsent(handlerName, GuiRecipeTab::getHandlerTitle); } @Override diff --git a/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java b/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java index 9bea115f..7fa0d760 100644 --- a/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java +++ b/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java @@ -3,6 +3,7 @@ package codechicken.nei.bookmark.tree; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -122,7 +123,7 @@ class StatsPanel extends ScrollContainer { GL11.glPushMatrix(); GL11.glColor4f(1, 1, 1, 1); - GL11.glTranslatef(badgeX, y - 6 * scale, 3.0f); + GL11.glTranslatef(badgeX, y - 6 * scale, 10.0f); GL11.glScalef(scale, scale, 1.0f); if (this.info.hasImageOrItem()) { @@ -715,8 +716,14 @@ class StatsPanel extends ScrollContainer { private List collectHandlers(RecipeChainMath math) { final Map handlerStats = new HashMap<>(); final List widgets = new ArrayList<>(); + final Set countedRecipes = new HashSet<>(); for (BookmarkItem item : math.recipeResults) { + // a recipe adds one result item per output, count it only once + if (!countedRecipes.add(item.recipeId)) { + continue; + } + final HandlerStats stats = handlerStats .computeIfAbsent(item.recipeId.getHandlerName(), k -> new HandlerStats()); stats.iterations += item.getMultiplier(); diff --git a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java index 6d0e729e..922960df 100644 --- a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java +++ b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java @@ -262,11 +262,8 @@ public class GuiOverlayButton extends GuiRecipeButton { .map(state -> state.getSlot().item).collect(Collectors.toList()); if (!items.isEmpty()) { - this.missedMaterialsTooltipLineHandler = new ItemsTooltipLineHandler( - NEIClientUtils.translate("recipe.overlay.missing"), - items, - true, - Integer.MAX_VALUE); + this.missedMaterialsTooltipLineHandler = ItemsTooltipLineHandler + .grid(NEIClientUtils.translate("recipe.overlay.missing"), items, Integer.MAX_VALUE); } else { this.missedMaterialsTooltipLineHandler = null; } diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipeTab.java b/src/main/java/codechicken/nei/recipe/GuiRecipeTab.java index 4eadd71d..e218c65c 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipeTab.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipeTab.java @@ -181,6 +181,24 @@ public abstract class GuiRecipeTab extends Widget { return res; } + public static String getHandlerTitle(String handlerName) { + String title = findRecipeName(GuiCraftingRecipe.craftinghandlers, handlerName); + if (title == null) title = findRecipeName(GuiCraftingRecipe.serialCraftingHandlers, handlerName); + if (title == null) title = findRecipeName(GuiUsageRecipe.usagehandlers, handlerName); + if (title == null) title = findRecipeName(GuiUsageRecipe.serialUsageHandlers, handlerName); + + return title != null ? title : handlerName; + } + + private static String findRecipeName(List handlers, String handlerName) { + for (int index = 0; index < handlers.size(); index++) { + final IRecipeHandler handler = handlers.get(index); + if (handlerName.equals(getHandlerInfo(handler).getHandlerName())) return handler.getRecipeName().trim(); + } + + return null; + } + public static String getHandlerMod(String name, String name2) { HandlerInfo info = getHandlerInfo(name, name2); if (info == null) return "Unknown"; diff --git a/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java b/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java index 6ea6d54e..4fc1478c 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java @@ -1,6 +1,5 @@ package codechicken.nei.recipe; -import java.awt.Dimension; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -16,10 +15,9 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; -import codechicken.lib.gui.GuiDraw.ITooltipLineHandler; import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.CompositeTooltipLineHandler; import codechicken.nei.ItemStackAmount; -import codechicken.nei.ItemsListTooltipLineHandler; import codechicken.nei.ItemsTooltipLineHandler; import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer; import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer; @@ -30,7 +28,7 @@ import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.Recipe.RecipeIngredient; import codechicken.nei.util.ReadableNumberConverter; -public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { +public class RecipeItemsTooltipLineHandler extends CompositeTooltipLineHandler { protected static class ChanceTotalAmountRenderer extends TotalAmountRenderer { @@ -86,16 +84,10 @@ public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { } } - protected static final int MAX_ROWS = 15; - protected final RecipeId recipeId; protected final long multiplier; protected boolean useInventory = false; - - protected final List lines = new ArrayList<>(); - protected final Dimension size = new Dimension(); protected String title = ""; - protected boolean created = false; public RecipeItemsTooltipLineHandler(RecipeId recipeId, long multiplier) { this.recipeId = recipeId; @@ -125,37 +117,12 @@ public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { return this.title; } - @Override - public Dimension getSize() { - ensureCreated(); - return this.size; - } - - protected void ensureCreated() { - if (!this.created) { - this.created = true; - createLines(); - } - } - - @Override - public void draw(int x, int y) { - if (this.size.height == 0) return; - - for (ItemsTooltipLineHandler line : this.lines) { - line.draw(x, y); - y += line.getSize().height; - } - } - protected static int getIngredientOrder(ItemStack stack) { return StackInfo.isFluidDisplayItem(stack) ? 2 : stack.stackSize == 0 ? 0 : 1; } + @Override protected void createLines() { - this.lines.clear(); - this.size.setSize(0, 0); - final RecipeHandlerRef handlerRef = RecipeHandlerRef.of(this.recipeId); if (handlerRef == null) { @@ -188,25 +155,17 @@ public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { addNeededLine( needed, approximateIngredients, - needed.size() == 1 || ingredientsAmount.size() + neededAmount.size() <= MAX_ROWS); + needed.size() == 1 || ingredientsAmount.size() + neededAmount.size() <= maxLineRows()); } } - int width = 0; - int height = 0; - - for (ItemsTooltipLineHandler line : this.lines) { - width = Math.max(width, line.getSize().width); - height += line.getSize().height; - } - - this.size.setSize(width, height); } protected void addResultLine(List items, Set approximate) { final String label = NEIClientUtils.translate("recipe.items.results"); - final ItemsTooltipLineHandler line = items.size() == 1 ? new ItemsListTooltipLineHandler(label, items, MAX_ROWS) - : new ItemsTooltipLineHandler(label, items); + final ItemsTooltipLineHandler line = items.size() == 1 + ? ItemsTooltipLineHandler.list(label, items, maxLineRows()) + : ItemsTooltipLineHandler.grid(label, items, maxLineRows()); if (line.isEmpty()) { return; @@ -229,7 +188,7 @@ public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { protected void addRequiredLine(List items, Set approximate) { final String label = NEIClientUtils.translate("recipe.items.ingredients"); - final ItemsTooltipLineHandler line = new ItemsListTooltipLineHandler(label, items, MAX_ROWS); + final ItemsTooltipLineHandler line = ItemsTooltipLineHandler.list(label, items, maxLineRows()); if (line.isEmpty()) { return; @@ -252,8 +211,8 @@ public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { protected void addNeededLine(List items, Set approximate, boolean vertical) { final String label = NEIClientUtils.translate("recipe.items.ingredients_needed"); - final ItemsTooltipLineHandler line = vertical ? new ItemsListTooltipLineHandler(label, items, MAX_ROWS) - : new ItemsTooltipLineHandler(label, items); + final ItemsTooltipLineHandler line = vertical ? ItemsTooltipLineHandler.list(label, items, maxLineRows()) + : ItemsTooltipLineHandler.grid(label, items, maxLineRows()); if (line.isEmpty()) { return; diff --git a/src/main/java/codechicken/nei/recipe/chain/RecipeChainTooltipLineHandler.java b/src/main/java/codechicken/nei/recipe/chain/RecipeChainTooltipLineHandler.java index 584a7ecd..80d1dfd1 100644 --- a/src/main/java/codechicken/nei/recipe/chain/RecipeChainTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/recipe/chain/RecipeChainTooltipLineHandler.java @@ -1,30 +1,123 @@ package codechicken.nei.recipe.chain; -import java.awt.Dimension; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; +import org.lwjgl.opengl.GL11; + import codechicken.lib.gui.GuiDraw; -import codechicken.lib.gui.GuiDraw.ITooltipLineHandler; +import codechicken.nei.ColorUtils; +import codechicken.nei.CompositeTooltipLineHandler; import codechicken.nei.ItemSorter; import codechicken.nei.ItemStackAmount; import codechicken.nei.ItemsTooltipLineHandler; -import codechicken.nei.NEIClientConfig; +import codechicken.nei.ItemsTooltipLineHandler.AmountRenderer; +import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer; +import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer; import codechicken.nei.NEIClientUtils; import codechicken.nei.bookmark.BookmarkItem; +import codechicken.nei.drawable.DrawableResource; +import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.AutoCraftingManager; import codechicken.nei.recipe.GuiRecipe; +import codechicken.nei.recipe.GuiRecipeTab; +import codechicken.nei.recipe.HandlerInfo; import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.StackInfo; -public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { +public class RecipeChainTooltipLineHandler extends CompositeTooltipLineHandler { + + protected static class MissingStacksAmountRenderer extends StacksAmountRenderer { + + protected static final int MISSING_COLOR = 0xFF5555; + + protected final Set missing; + private final Map cache = new IdentityHashMap<>(); + + public MissingStacksAmountRenderer(Set missing) { + this.missing = missing; + } + + @Override + protected int getColor(ItemStack stack) { + return this.cache.computeIfAbsent(stack, key -> this.missing.contains(StackInfo.itemStackToNBT(key, false))) + ? MISSING_COLOR + : super.getColor(stack); + } + } + + protected static class HandlerIconRenderer extends TotalAmountRenderer { + + protected static final int ICON_SIZE = 16; + protected static final float BADGE_SCALE = 0.6f; + protected static final float BADGE_OFFSET = 3f; + // slots are drawn edge to edge, so the badge has to clear the 3d model of the neighbour slot + protected static final float BADGE_Z = 10f; + + protected final Map handlers; + private final Map cache = new IdentityHashMap<>(); + private final Map titleCache = new HashMap<>(); + + public HandlerIconRenderer(Map handlers) { + this.handlers = handlers; + } + + @Override + public void draw(int x, int y, ItemStack stack, long amount) { + super.draw(x, y, stack, amount); + drawHandlerBadge( + x, + y, + this.cache.computeIfAbsent(stack, key -> this.handlers.get(StackInfo.itemStackToNBT(key, false)))); + } + + protected void drawHandlerBadge(int x, int y, HandlerInfo handlerInfo) { + if (handlerInfo == null) return; + + GL11.glPushMatrix(); + GL11.glColor4f(1, 1, 1, 1); + GL11.glTranslatef(x - BADGE_OFFSET * BADGE_SCALE, y - BADGE_OFFSET * BADGE_SCALE, BADGE_Z); + GL11.glScalef(BADGE_SCALE, BADGE_SCALE, 1.0f); + + if (handlerInfo.hasImageOrItem()) { + final DrawableResource image = handlerInfo.getImage(); + + if (image != null) { + NEIClientUtils.gl2DRenderContext(() -> image.draw(0, 0)); + } else { + GuiContainerManager.drawItem(0, 0, handlerInfo.getItemStack(), true, ""); + } + + } else { + drawBadgeText(handlerInfo.getHandlerName()); + } + + GL11.glPopMatrix(); + } + + protected void drawBadgeText(String handlerName) { + final String title = this.titleCache.computeIfAbsent(handlerName, GuiRecipeTab::getHandlerTitle); + final String text = title.isEmpty() ? "??" : title.substring(0, Math.min(2, title.length())); + + NEIClientUtils.gl2DRenderContext( + () -> GuiDraw.fontRenderer.drawStringWithShadow( + text, + (ICON_SIZE - GuiDraw.fontRenderer.getStringWidth(text)) / 2, + (ICON_SIZE - GuiDraw.fontRenderer.FONT_HEIGHT) / 2, + ColorUtils.buttonLabelNormal.getColor())); + } + } public final int groupId; public final boolean crafting; @@ -32,16 +125,9 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { protected final List initialItems; protected final Map outputRecipes; - protected ItemsTooltipLineHandler available; - protected ItemsTooltipLineHandler inputs; - protected ItemsTooltipLineHandler outputs; - protected ItemsTooltipLineHandler remainder; - protected ItemsTooltipLineHandler craftingNeeded; protected boolean lastShiftKey = false; protected boolean lastControlKey = false; - protected Dimension size = new Dimension(); - public RecipeChainTooltipLineHandler(int groupId, boolean crafting, RecipeChainMath math) { this.groupId = groupId; this.crafting = crafting; @@ -50,20 +136,32 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { this.outputRecipes = new HashMap<>(this.math.outputRecipes); } - private void onUpdate() { + @Override + protected boolean needsUpdate() { + final boolean shiftChanged = this.lastShiftKey != (this.lastShiftKey = NEIClientUtils.shiftKey()); + return this.lastControlKey != (this.lastControlKey = NEIClientUtils.controlKey()) || shiftChanged; + } + + @Override + protected void createLines() { + if (this.outputRecipes.isEmpty() && !this.lastShiftKey) { + return; + } + final List available = new ArrayList<>(); final List inputs = new ArrayList<>(); final List outputs = new ArrayList<>(); final List remainder = new ArrayList<>(); final List craftingNeeded = new ArrayList<>(); + final Map craftingHandlers = new HashMap<>(); final ItemStackAmount inventory = new ItemStackAmount(); final GuiContainer currentGui = NEIClientUtils.getGuiContainer(); - if (this.lastShiftKey && !(currentGui instanceof GuiRecipe)) { + if (this.lastShiftKey && currentGui != null && !(currentGui instanceof GuiRecipe)) { inventory.putAll(AutoCraftingManager.getInventoryItems(currentGui)); } - if (!this.math.outputRecipes.isEmpty()) { + if (!this.outputRecipes.isEmpty()) { this.math.initialItems.clear(); this.math.outputRecipes.clear(); this.math.outputRecipes.putAll(this.outputRecipes); @@ -105,7 +203,7 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { } } else { - this.math.initialItems.addAll(initialItems); + this.math.initialItems.addAll(this.initialItems); } this.math.refresh(); @@ -138,26 +236,24 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { if (amount > 0) { if (math.outputRecipes.containsKey(item.recipeId)) { outputs.add(item.getItemStack(amount)); - } else if (this.lastShiftKey) { + } else { remainder.add(item.getItemStack(amount)); } } } - if (this.lastShiftKey) { - for (ItemStack stack : math.containerItemsInventory) { - if (stack != null) { - remainder.add(stack.copy()); - } + for (ItemStack stack : math.containerItemsInventory) { + if (stack != null) { + remainder.add(stack.copy()); } - for (ItemStack stack : math.containerItemsCrafting) { - if (stack != null) { - remainder.add(stack.copy()); - } + } + for (ItemStack stack : math.containerItemsCrafting) { + if (stack != null) { + remainder.add(stack.copy()); } } - } else if (this.lastShiftKey) { + } else { for (BookmarkItem item : this.math.initialItems) { if (inventory.contains(item.itemStack)) { @@ -175,161 +271,100 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler { inputs.add(item.getItemStack()); } } - } + if (this.lastShiftKey) { for (Map.Entry item : this.math.requiredAmount.entrySet()) { - if (item.getKey().type == BookmarkItem.BookmarkItemType.RESULT && item.getValue() != 0) - craftingNeeded.add(item.getKey().getItemStack(item.getValue())); + if (item.getKey().type == BookmarkItem.BookmarkItemType.RESULT && item.getValue() != 0) { + final ItemStack stack = item.getKey().getItemStack(item.getValue()); + + craftingNeeded.add(stack); + putHandlerInfo(craftingHandlers, stack, item.getKey().recipeId); + } } } - inputs.sort( - Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null) - .thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance)); - outputs.sort( - Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null) - .thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance)); - remainder.sort( - Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null) - .thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance)); - - this.inputs = new ItemsTooltipLineHandler( - this.lastShiftKey ? NEIClientUtils.translate("bookmark.crafting_chain.missing") - : NEIClientUtils.translate("bookmark.crafting_chain.input"), - inputs, - true, - Integer.MAX_VALUE); - - this.available = new ItemsTooltipLineHandler( - NEIClientUtils.translate("bookmark.crafting_chain.available"), - available, - true, - Integer.MAX_VALUE); - - this.outputs = new ItemsTooltipLineHandler( - NEIClientUtils.translate("bookmark.crafting_chain.output"), - outputs, - true, - Integer.MAX_VALUE); - - this.remainder = new ItemsTooltipLineHandler( - NEIClientUtils.translate("bookmark.crafting_chain.remainder"), - remainder, - true, - Integer.MAX_VALUE); - - this.craftingNeeded = new ItemsTooltipLineHandler( - NEIClientUtils.translate("bookmark.crafting_chain.needed"), - craftingNeeded, - true, - Integer.MAX_VALUE); + final Set missing = new HashSet<>(); if (this.lastShiftKey) { - this.inputs.setLabelColor(EnumChatFormatting.RED); - this.available.setLabelColor(EnumChatFormatting.GREEN); - this.craftingNeeded.setLabelColor(EnumChatFormatting.BLUE); - } - - this.size.height = this.size.width = 0; - - if (!this.inputs.isEmpty() || !this.outputs.isEmpty() - || !this.remainder.isEmpty() - || !this.available.isEmpty()) { - - if (!this.math.outputRecipes.isEmpty()) { - this.size.height = 2 + GuiDraw.fontRenderer.FONT_HEIGHT; + for (ItemStack stack : inputs) { + missing.add(StackInfo.itemStackToNBT(stack, false)); } - this.size.width = Math.max( - this.inputs.getSize().width, - Math.max( - this.outputs.getSize().width, - Math.max( - this.remainder.getSize().width, - Math.max(this.available.getSize().width, this.craftingNeeded.getSize().width)))); - - this.size.height += this.inputs.getSize().height + this.outputs.getSize().height - + this.remainder.getSize().height - + this.available.getSize().height - + this.craftingNeeded.getSize().height; } + // items with a shortage are already in the inputs list, holding the missing amount instead of the required one + final List covered = new ArrayList<>(); + + for (ItemStack stack : available) { + if (!missing.contains(StackInfo.itemStackToNBT(stack, false))) { + covered.add(stack); + } + } + + final Comparator comparator = Comparator + .comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null) + .thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance); + + inputs.sort(comparator); + covered.sort(comparator); + outputs.sort(comparator); + remainder.sort(comparator); + + // missing items stay on top of the list + inputs.addAll(covered); + + if (this.lastShiftKey) { + outputs.addAll(remainder); + } + + addLine( + NEIClientUtils.translate("bookmark.crafting_chain"), + outputs, + outputs.size() == 1, + EnumChatFormatting.AQUA, + new TotalAmountRenderer()); + + addLine( + NEIClientUtils.translate("bookmark.crafting_chain.needed"), + craftingNeeded, + false, + EnumChatFormatting.BLUE, + new HandlerIconRenderer(craftingHandlers)); + + addLine( + NEIClientUtils.translate("bookmark.crafting_chain.input"), + inputs, + this.lastShiftKey, + EnumChatFormatting.YELLOW, + this.lastShiftKey ? new MissingStacksAmountRenderer(missing) : new TotalAmountRenderer()); } - @Override - public Dimension getSize() { - boolean update = this.outputs == null; - update = this.lastShiftKey != (this.lastShiftKey = NEIClientUtils.shiftKey()) || update; - update = this.lastControlKey != (this.lastControlKey = NEIClientUtils.controlKey()) || update; - - if (update) { - onUpdate(); + protected static void putHandlerInfo(Map handlers, ItemStack stack, + RecipeId recipeId) { + if (recipeId == null) { + return; } - return this.size; + + final HandlerInfo handlerInfo = GuiRecipeTab.getHandlerInfo(recipeId.getHandlerName(), null); + + handlers.put( + StackInfo.itemStackToNBT(stack, false), + handlerInfo != null ? handlerInfo : GuiRecipeTab.DEFAULT_HANDLER_INFO); } - @Override - public void draw(int x, int y) { - if (this.size.height == 0) return; + protected void addLine(String label, List items, boolean vertical, EnumChatFormatting labelColor, + AmountRenderer amountRenderer) { + final ItemsTooltipLineHandler line = vertical ? ItemsTooltipLineHandler.list(label, items, maxLineRows()) + : ItemsTooltipLineHandler.grid(label, items, maxLineRows()); - if (!this.math.outputRecipes.isEmpty()) { - GuiDraw.fontRenderer.drawStringWithShadow( - EnumChatFormatting.AQUA + NEIClientUtils.translate("bookmark.crafting_chain"), - x, - y + 2, - 0xee555555); - - y += 2 + GuiDraw.fontRenderer.FONT_HEIGHT; + if (line.isEmpty()) { + return; } - if (NEIClientConfig.recipeChainDir() == 0) { - if (!this.inputs.isEmpty()) { - this.inputs.draw(x, y); - y += this.inputs.getSize().height; - } - - if (!this.available.isEmpty()) { - this.available.draw(x, y); - y += this.available.getSize().height; - } - - if (!this.craftingNeeded.isEmpty()) { - this.craftingNeeded.draw(x, y); - y += this.craftingNeeded.getSize().height; - } - - if (!this.outputs.isEmpty()) { - this.outputs.draw(x, y); - y += this.outputs.getSize().height; - } - - } else { - - if (!this.outputs.isEmpty()) { - this.outputs.draw(x, y); - y += this.outputs.getSize().height; - } - - if (!this.inputs.isEmpty()) { - this.inputs.draw(x, y); - y += this.inputs.getSize().height; - } - - if (!this.craftingNeeded.isEmpty()) { - this.craftingNeeded.draw(x, y); - y += this.craftingNeeded.getSize().height; - } - - if (!this.available.isEmpty()) { - this.available.draw(x, y); - y += this.available.getSize().height; - } - } - - if (!this.remainder.isEmpty()) { - this.remainder.draw(x, y); - } + line.setLabelColor(labelColor); + line.setAmountRenderer(amountRenderer); + this.lines.add(line); } } diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 4e55a7e6..87874ab3 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -48,11 +48,7 @@ nei.bookmark.recipe.toggle_collapse=Toggle Collapse/Expand nei.bookmark.recipe.chat_link=Send Recipe to Chat nei.bookmark.crafting_chain=Crafting Chain nei.bookmark.crafting_chain.input=Ingredients -nei.bookmark.crafting_chain.missing=Missing Items -nei.bookmark.crafting_chain.output=Results -nei.bookmark.crafting_chain.remainder=Remainders nei.bookmark.crafting_chain.needed=Required Crafts -nei.bookmark.crafting_chain.available=Available Items nei.bookmark.tree.title=Crafting Tree nei.bookmark.tree.empty=No crafting chain to display nei.bookmark.tree.overlay=Use for recipe tree @@ -284,9 +280,6 @@ nei.options.inventory.bookmarks.enabled.false=Hidden nei.options.inventory.bookmarks.animation=REI Style Animation in Bookmarks nei.options.inventory.bookmarks.animation.true=Yes nei.options.inventory.bookmarks.animation.false=No -nei.options.inventory.bookmarks.recipeChainDir=Crafting Chain Info Direction -nei.options.inventory.bookmarks.recipeChainDir.0=Input First -nei.options.inventory.bookmarks.recipeChainDir.1=Output First nei.options.inventory.bookmarks.recipeTooltipsMode=Show Recipe Tooltips in Bookmarks nei.options.inventory.bookmarks.recipeTooltipsMode.0=None nei.options.inventory.bookmarks.recipeTooltipsMode.1=In Horizontal View diff --git a/src/main/resources/assets/nei/lang/ru_RU.lang b/src/main/resources/assets/nei/lang/ru_RU.lang index 6a1da14f..5e170214 100644 --- a/src/main/resources/assets/nei/lang/ru_RU.lang +++ b/src/main/resources/assets/nei/lang/ru_RU.lang @@ -23,8 +23,6 @@ nei.bookmark.group.pull_items_shift=Переместить недостающи nei.bookmark.group.chat_link=Отправить группу в чат nei.bookmark.crafting_chain=Цепочка крафта nei.bookmark.crafting_chain.input=Ингредиенты -nei.bookmark.crafting_chain.output=Результаты -nei.bookmark.crafting_chain.remainder=Остатки nei.itempanel.quantity.default=Одна стопка nei.itempanel.loading=Загрузка списка предметов... @@ -184,9 +182,6 @@ nei.options.inventory.bookmarks.enabled.false=Скрытая nei.options.inventory.bookmarks.animation=Анимация в стиле REI в закладках nei.options.inventory.bookmarks.animation.true=Да nei.options.inventory.bookmarks.animation.false=Нет -nei.options.inventory.bookmarks.recipeChainDir=Направление информации о цепочке крафта -nei.options.inventory.bookmarks.recipeChainDir.0=Сначала ингредиенты -nei.options.inventory.bookmarks.recipeChainDir.1=Сначала результат nei.options.inventory.bookmarks.recipeTooltipsMode=Показывать подсказки рецептов в закладках nei.options.inventory.bookmarks.recipeTooltipsMode.0=Нет nei.options.inventory.bookmarks.recipeTooltipsMode.1=В горизонтальном виде diff --git a/src/main/resources/assets/nei/lang/uk_UA.lang b/src/main/resources/assets/nei/lang/uk_UA.lang index 69a162bc..fb802921 100644 --- a/src/main/resources/assets/nei/lang/uk_UA.lang +++ b/src/main/resources/assets/nei/lang/uk_UA.lang @@ -47,11 +47,7 @@ nei.bookmark.recipe.toggle_collapse=Згорнути/Розгорнути nei.bookmark.recipe.chat_link=Надіслати рецепт у чат nei.bookmark.crafting_chain=Ланцюжок крафту nei.bookmark.crafting_chain.input=Інгредієнти -nei.bookmark.crafting_chain.missing=Відсутні предмети -nei.bookmark.crafting_chain.output=Результати -nei.bookmark.crafting_chain.remainder=Залишки nei.bookmark.crafting_chain.needed=Необхідні крафти -nei.bookmark.crafting_chain.available=Доступні предмети nei.inventory.search.placeholder=Пошук... nei.itempanel.quantity.default=Один стек nei.itempanel.loading=Завантаження списку предметів... @@ -236,9 +232,6 @@ nei.options.inventory.bookmarks.enabled.false=Прихована nei.options.inventory.bookmarks.animation=Анімація закладок у стилі REI nei.options.inventory.bookmarks.animation.true=Так nei.options.inventory.bookmarks.animation.false=Ні -nei.options.inventory.bookmarks.recipeChainDir=Напрямок ланцюжка крафту -nei.options.inventory.bookmarks.recipeChainDir.0=Спочатку інгредієнти -nei.options.inventory.bookmarks.recipeChainDir.1=Спочатку результат nei.options.inventory.bookmarks.recipeTooltipsMode=Показувати підказки рецептів у закладках nei.options.inventory.bookmarks.recipeTooltipsMode.0=Ніколи nei.options.inventory.bookmarks.recipeTooltipsMode.1=У горизонтальному вигляді