diff --git a/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java b/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java new file mode 100644 index 00000000..4aad640e --- /dev/null +++ b/src/main/java/codechicken/nei/ItemsListTooltipLineHandler.java @@ -0,0 +1,125 @@ +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 2ff46a40..1471bde8 100644 --- a/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/ItemsTooltipLineHandler.java @@ -4,6 +4,7 @@ import static codechicken.lib.gui.GuiDraw.fontRenderer; import java.awt.Dimension; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -17,22 +18,151 @@ import org.lwjgl.opengl.GL12; import codechicken.lib.gui.GuiDraw; import codechicken.lib.gui.GuiDraw.ITooltipLineHandler; +import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.NEIClientUtils.Alignment; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.ReadableNumberConverter; public class ItemsTooltipLineHandler implements ITooltipLineHandler { + @FunctionalInterface + public interface AmountRenderer { + + void draw(int x, int y, ItemStack stack, long amount); + } + + public static class NoAmountRenderer implements AmountRenderer { + + @Override + public void draw(int x, int y, ItemStack stack, long amount) { + GuiContainerManager.drawItem(x, y, stack, true, ""); + } + + protected static void drawAmountText(String text, Rectangle4i rect, int color, Alignment alignment) { + NEIClientUtils.drawNEIOverlayText(text, rect, 1, color, true, alignment); + } + } + + public static class TotalAmountRenderer extends NoAmountRenderer { + + private static class AmountText { + + public final String text; + public final Alignment alignment; + + public AmountText(String text, Alignment alignment) { + this.text = text; + this.alignment = alignment; + } + } + + private final Map cache = new IdentityHashMap<>(); + + @Override + public void draw(int x, int y, ItemStack stack, long amount) { + super.draw(x, y, stack, amount); + drawAmount(new Rectangle4i(x, y, SLOT_SIZE - 2, SLOT_SIZE - 2), stack, amount); + } + + protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) { + final AmountText amountText = this.cache.computeIfAbsent(stack, key -> { + final boolean isFluidDisplay = StackInfo.isFluidDisplayItem(key); + final String text = formatAmount(key, amount); + + return new AmountText( + isFluidDisplay && !text.isEmpty() ? text + "L" : text, + isFluidDisplay ? Alignment.BottomLeft : Alignment.BottomRight); + }); + + if (!amountText.text.isEmpty()) { + drawAmountText(amountText.text, rect, 0xFFFFFF, amountText.alignment); + } + } + + protected String formatAmount(ItemStack stack, long amount) { + return amount == 0 ? "" : ReadableNumberConverter.INSTANCE.toWideReadableForm(amount); + } + } + + public static class StacksAmountRenderer extends NoAmountRenderer { + + private static class StacksText { + + public final String stacks; + public final String remainder; + public final int color; + + public StacksText(String stacks, String remainder, int color) { + this.stacks = stacks; + this.remainder = remainder; + this.color = color; + } + } + + private final Map cache = new IdentityHashMap<>(); + + @Override + public void draw(int x, int y, ItemStack stack, long amount) { + super.draw(x, y, stack, amount); + drawAmount(new Rectangle4i(x, y, SLOT_SIZE - 2, SLOT_SIZE - 2), stack, amount); + } + + protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) { + final StacksText stacksText = this.cache.computeIfAbsent(stack, key -> formatAmount(key, amount)); + + if (!stacksText.stacks.isEmpty()) { + drawAmountText(stacksText.stacks, rect, stacksText.color, Alignment.TopLeft); + } + + if (!stacksText.remainder.isEmpty()) { + drawAmountText(stacksText.remainder, rect, stacksText.color, Alignment.BottomRight); + } + } + + private StacksText formatAmount(ItemStack stack, long amount) { + final long stackSize = StackInfo.isFluidDisplayItem(stack) ? FLUID_STACK_SIZE + : Math.max(1, stack.getMaxStackSize()); + final long stacks = amount / stackSize; + final long remainder = amount % stackSize; + final String prefix = getPrefix(stack); + + return new StacksText( + stacks > 0 ? prefix + "x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(stacks) : "", + remainder > 0 + ? (stacks > 0 ? "+" : prefix) + + ReadableNumberConverter.INSTANCE.toWideReadableForm(remainder) + : "", + getColor(stack)); + } + + protected String getPrefix(ItemStack stack) { + return ""; + } + + protected int getColor(ItemStack stack) { + return 0xFFFFFF; + } + } + protected static final int SLOT_SIZE = 18; + protected static final int ICON_SIZE = 16; + 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 FLUID_STACK_SIZE = 144; + + protected final List items; + protected final List amounts = new ArrayList<>(); + + protected AmountRenderer amountRenderer; protected String label; protected EnumChatFormatting labelColor = EnumChatFormatting.GRAY; - protected List items; + protected int activeStackIndex = -1; protected Dimension size = new Dimension(); - protected boolean saveStackSize = false; + protected int columns = 0; protected int count = 0; protected int rows = 0; @@ -45,7 +175,7 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { public ItemsTooltipLineHandler(String label, List items, boolean saveStackSize, int maxRows) { this.label = label; this.items = groupingItemStacks(items); - this.saveStackSize = saveStackSize; + this.amountRenderer = saveStackSize ? new TotalAmountRenderer() : new NoAmountRenderer(); this.length = this.items.size(); if (this.length > 0) { @@ -69,6 +199,11 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { } + public ItemsTooltipLineHandler setAmountRenderer(AmountRenderer amountRenderer) { + this.amountRenderer = amountRenderer; + return this; + } + public void setActiveStack(ItemStack activeStack) { final ItemStack realStack = items.stream().filter(stack -> NEIClientUtils.areStacksSameType(stack, activeStack)) .findFirst().orElse(null); @@ -126,19 +261,15 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { } for (int index = 0; index < this.count && index + indexShift < this.items.size(); index++) { - ItemStack drawStack = this.items.get(index + indexShift); int col = index % this.columns; int row = index / this.columns; - String stackSize = !this.saveStackSize || drawStack.stackSize == 0 ? "" - : ReadableNumberConverter.INSTANCE.toWideReadableForm(drawStack.stackSize); - if (this.activeStackIndex == index + indexShift) { NEIClientUtils.gl2DRenderContext( - () -> GuiDraw.drawRect(col * SLOT_SIZE - 1, row * SLOT_SIZE - 1, 18, 18, 0x66555555)); + () -> GuiDraw.drawRect(col * SLOT_SIZE, row * SLOT_SIZE, SLOT_SIZE, SLOT_SIZE, 0x66555555)); } - drawItem(col * SLOT_SIZE, row * SLOT_SIZE, drawStack, stackSize); + drawStackWithAmount(col * SLOT_SIZE, row * SLOT_SIZE, index + indexShift); } if (this.count < this.items.size()) { @@ -157,25 +288,23 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler { GL11.glPopAttrib(); } - protected void drawItem(int x, int y, ItemStack drawStack, String stackSize) { - GuiContainerManager.drawItem(x, y, drawStack, true, stackSize); + 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)); } private List groupingItemStacks(List items) { final List result = new ArrayList<>(); for (Map.Entry entry : ItemStackAmount.of(items).entrySet()) { - final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), Math.max(0, entry.getValue())); - - if (stack != null && StackInfo.isFluidDisplayItem(stack)) { - stack.stackSize = 0; - } - - result.add(stack); + result.add(StackInfo.loadFromNBT(entry.getKey(), 0)); + this.amounts.add(Math.max(0, entry.getValue())); } if (result.isEmpty()) { - result.addAll(items); + for (ItemStack stack : items) { + result.add(stack); + this.amounts.add(0L); + } } return result; diff --git a/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java b/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java index 37ac8b84..7b9f158b 100644 --- a/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java +++ b/src/main/java/codechicken/nei/bookmark/tree/GuiCraftingTree.java @@ -67,6 +67,7 @@ import codechicken.nei.recipe.GuiRecipeButton.UpdateRecipeButtonsEvent; import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.Recipe; import codechicken.nei.recipe.Recipe.RecipeId; +import codechicken.nei.recipe.RecipeItemsTooltipLineHandler; import codechicken.nei.recipe.RecipeTooltipLineHandler; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.EmptyContainer; @@ -134,6 +135,7 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu private RecipeTooltipLineHandler recipeTooltipLineHandler; private AcceptsFollowingTooltipLineHandler acceptsFollowingTooltipLineHandler; + private RecipeItemsTooltipLineHandler recipeItemsTooltipLineHandler; private final CraftingTreeState uiState = new CraftingTreeState(); private final CraftingTreeToolbar toolbar; @@ -939,12 +941,15 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu if (toolbarTooltip != null) { currenttip.add(toolbarTooltip); } else if (this.hoveredBadgeHandler != null) { - final RecipeId recipeId = this.hoveredBadgeHandler.prefItem != null - ? this.hoveredBadgeHandler.prefItem.recipeId - : null; - final long multiplier = this.hoveredBadgeHandler.multiplier; + final ItemTreeSlot node = this.hoveredBadgeHandler; + final RecipeId recipeId = node.prefItem != null ? node.prefItem.recipeId : null; + + if (recipeId != null && NEIClientUtils.shiftKey()) { + addRecipeItemsTooltipLine(recipeId, node.multiplier, currenttip); + } else { + addRecipeTooltipLine(recipeId, node.multiplier, currenttip); + } - addRecipeTooltipLine(recipeId, multiplier, currenttip); } else { if (this.statsPanelVisible) { @@ -960,6 +965,14 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu EnumChatFormatting.GRAY + NEIClientUtils.translate( "bookmark.tree.stats.handler_iterations", handlerStatsSlot.stats.iterations)); + } else if (widget instanceof StatsPanel.ItemStatsSlot itemStatsSlot && itemStatsSlot.recipeId != null) { + if (NEIClientUtils.shiftKey()) { + addRecipeItemsTooltipLine(itemStatsSlot.recipeId, itemStatsSlot.multiplier, currenttip); + } else { + currenttip.add(itemStatsSlot.stack.getDisplayName() + GuiDraw.TOOLTIP_LINESPACE); + + addRecipeTooltipLine(itemStatsSlot.recipeId, itemStatsSlot.multiplier, currenttip); + } } } @@ -968,6 +981,19 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu return currenttip; } + private void addRecipeItemsTooltipLine(RecipeId recipeId, long multiplier, List currenttip) { + + if (this.recipeItemsTooltipLineHandler == null + || this.recipeItemsTooltipLineHandler.getMultiplier() != multiplier + || !this.recipeItemsTooltipLineHandler.getRecipeId().equals(recipeId)) { + this.recipeItemsTooltipLineHandler = new RecipeItemsTooltipLineHandler(recipeId, multiplier) + .setUseInventory(true); + } + + currenttip.add(this.recipeItemsTooltipLineHandler.getTitle() + GuiDraw.TOOLTIP_LINESPACE); + currenttip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.recipeItemsTooltipLineHandler)); + } + private void addRecipeTooltipLine(RecipeId recipeId, long multiplier, List currenttip) { if (recipeId == null) { @@ -1078,14 +1104,6 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu } else { this.acceptsFollowingTooltipLineHandler = null; - - if (this.statsPanelVisible) { - final Widget widget = this.statsPanel.getWidgetUnderMouse(mousex, mousey); - if (widget instanceof StatsPanel.ItemStatsSlot itemStatsSlot) { - addRecipeTooltipLine(itemStatsSlot.recipeId, itemStatsSlot.multiplier, currenttip); - } - } - } return currenttip; diff --git a/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java b/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java index 14403435..9bea115f 100644 --- a/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java +++ b/src/main/java/codechicken/nei/bookmark/tree/StatsPanel.java @@ -88,6 +88,10 @@ class StatsPanel extends ScrollContainer { this(stack, stackSize, icon, null, 1); } + public ItemStack getStack() { + return StackInfo.withAmount(this.stack, stackSize); + } + @Override public void draw(int mx, int my) { @@ -519,7 +523,7 @@ class StatsPanel extends ScrollContainer { public ItemStack getStackMouseOver(int mouseX, int mouseY) { final Widget widget = getWidgetUnderMouse(mouseX, mouseY); - return widget instanceof ItemStatsSlot slot ? slot.stack : null; + return widget instanceof ItemStatsSlot slot && slot.recipeId == null ? slot.getStack() : null; } public void setHighlight(SearchResult searchResult) { diff --git a/src/main/java/codechicken/nei/recipe/AcceptsFollowingTooltipLineHandler.java b/src/main/java/codechicken/nei/recipe/AcceptsFollowingTooltipLineHandler.java index 6343fb87..944efbaa 100644 --- a/src/main/java/codechicken/nei/recipe/AcceptsFollowingTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/recipe/AcceptsFollowingTooltipLineHandler.java @@ -20,6 +20,7 @@ public class AcceptsFollowingTooltipLineHandler extends ItemsTooltipLineHandler super(NEIClientUtils.translate("recipe.accepts"), items, false, maxRows); this.tooltipGUID = tooltipGUID; setActiveStack(activeStack); + setAmountRenderer(new FavoriteAmountRenderer()); } public static AcceptsFollowingTooltipLineHandler of(Object tooltipGUID, List items, @@ -38,12 +39,15 @@ public class AcceptsFollowingTooltipLineHandler extends ItemsTooltipLineHandler return null; } - @Override - protected void drawItem(int x, int y, ItemStack drawStack, String stackSize) { - super.drawItem(x, y, drawStack, stackSize); + protected static class FavoriteAmountRenderer extends NoAmountRenderer { - if (FavoriteRecipes.containsManual(drawStack)) { - NEIClientUtils.drawNEIOverlayText("F", x, y); + @Override + public void draw(int x, int y, ItemStack stack, long amount) { + super.draw(x, y, stack, amount); + + if (FavoriteRecipes.containsManual(stack)) { + NEIClientUtils.drawNEIOverlayText("F", x, y); + } } } diff --git a/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java b/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java new file mode 100644 index 00000000..6ea6d54e --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/RecipeItemsTooltipLineHandler.java @@ -0,0 +1,332 @@ +package codechicken.nei.recipe; + +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +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.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +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.ItemStackAmount; +import codechicken.nei.ItemsListTooltipLineHandler; +import codechicken.nei.ItemsTooltipLineHandler; +import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer; +import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PositionedStack; +import codechicken.nei.api.IGuiContainerOverlay; +import codechicken.nei.recipe.Recipe.RecipeId; +import codechicken.nei.recipe.Recipe.RecipeIngredient; +import codechicken.nei.util.ReadableNumberConverter; + +public class RecipeItemsTooltipLineHandler implements ITooltipLineHandler { + + protected static class ChanceTotalAmountRenderer extends TotalAmountRenderer { + + protected final Set approximate; + + public ChanceTotalAmountRenderer(Set approximate) { + this.approximate = approximate; + } + + @Override + protected String formatAmount(ItemStack stack, long amount) { + final String text = super.formatAmount(stack, amount); + + if (!this.approximate.contains(stack)) { + return text; + } else if (!text.isEmpty()) { + return EnumChatFormatting.GOLD + "~" + text; + } else if (amount == 0) { + return EnumChatFormatting.GOLD + "~0"; + } + + return text; + } + } + + protected static class ChanceStacksAmountRenderer extends StacksAmountRenderer { + + protected static final int APPROXIMATE_COLOR = 0xFFAA00; + + protected final Set approximate; + + public ChanceStacksAmountRenderer(Set approximate) { + this.approximate = approximate; + } + + @Override + protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) { + if (amount == 0) { + Badge.notConsumed().draw(rect); + } else { + super.drawAmount(rect, stack, amount); + } + } + + @Override + protected String getPrefix(ItemStack stack) { + return this.approximate.contains(stack) ? "~" : ""; + } + + @Override + protected int getColor(ItemStack stack) { + return this.approximate.contains(stack) ? APPROXIMATE_COLOR : super.getColor(stack); + } + } + + 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; + this.multiplier = multiplier; + } + + public RecipeItemsTooltipLineHandler setUseInventory(boolean useInventory) { + this.useInventory = useInventory; + this.created = false; + return this; + } + + public RecipeId getRecipeId() { + return this.recipeId; + } + + public long getMultiplier() { + return this.multiplier; + } + + public boolean isUseInventory() { + return this.useInventory; + } + + public String getTitle() { + ensureCreated(); + 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; + } + + protected void createLines() { + this.lines.clear(); + this.size.setSize(0, 0); + + final RecipeHandlerRef handlerRef = RecipeHandlerRef.of(this.recipeId); + + if (handlerRef == null) { + return; + } + + final Recipe recipe = Recipe.of(handlerRef); + final Set approximateResults = new HashSet<>(); + final Set approximateIngredients = new HashSet<>(); + final List results = multiplyItems(recipe.getResults(), false, approximateResults); + final List ingredients = multiplyItems(recipe.getIngredients(), true, approximateIngredients); + final ItemStackAmount ingredientsAmount = ItemStackAmount.of(ingredients); + + ingredients.sort(Comparator.comparingInt(RecipeItemsTooltipLineHandler::getIngredientOrder)); + + this.title = handlerRef.handler.getRecipeName().trim() + " (x" + + ReadableNumberConverter.INSTANCE.toWideReadableForm(this.multiplier) + + ")"; + + addResultLine(results, approximateResults); + addRequiredLine(ingredients, approximateIngredients); + + if (this.useInventory) { + final List needed = subtractItems(ingredientsAmount, getInventoryItems()); + final ItemStackAmount neededAmount = ItemStackAmount.of(needed); + final ItemStackAmount consumedAmount = ItemStackAmount.of(ingredientsAmount); + consumedAmount.removeIf(entry -> entry.getValue() <= 0); + + if (!neededAmount.equals(consumedAmount)) { + addNeededLine( + needed, + approximateIngredients, + needed.size() == 1 || ingredientsAmount.size() + neededAmount.size() <= MAX_ROWS); + } + } + + 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); + + if (line.isEmpty()) { + return; + } + + line.setLabelColor(EnumChatFormatting.GREEN); + + final Set approximateItems = Collections.newSetFromMap(new IdentityHashMap<>()); + + for (ItemStack stack : line.getItems()) { + if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) { + approximateItems.add(stack); + } + } + + line.setAmountRenderer(new ChanceTotalAmountRenderer(approximateItems)); + + this.lines.add(line); + } + + protected void addRequiredLine(List items, Set approximate) { + final String label = NEIClientUtils.translate("recipe.items.ingredients"); + final ItemsTooltipLineHandler line = new ItemsListTooltipLineHandler(label, items, MAX_ROWS); + + if (line.isEmpty()) { + return; + } + + line.setLabelColor(EnumChatFormatting.YELLOW); + + final Set approximateItems = Collections.newSetFromMap(new IdentityHashMap<>()); + + for (ItemStack stack : line.getItems()) { + if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) { + approximateItems.add(stack); + } + } + + line.setAmountRenderer(new ChanceStacksAmountRenderer(approximateItems)); + + this.lines.add(line); + } + + 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); + + if (line.isEmpty()) { + return; + } + + line.setLabelColor(EnumChatFormatting.RED); + + final Set approximateItems = Collections.newSetFromMap(new IdentityHashMap<>()); + + for (ItemStack stack : line.getItems()) { + if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) { + approximateItems.add(stack); + } + } + + line.setAmountRenderer(new ChanceStacksAmountRenderer(approximateItems)); + + this.lines.add(line); + } + + protected List multiplyItems(List items, boolean ingredients, + Set approximate) { + final ItemStackAmount chanceAmounts = new ItemStackAmount(); + final List result = new ArrayList<>(); + + for (RecipeIngredient item : items) { + chanceAmounts.add(item.getItemStack(), (long) item.getAmount() * item.getChance()); + } + + for (Map.Entry entry : chanceAmounts.entrySet()) { + final long amount = entry.getValue() * this.multiplier; + final long entryAmount = ingredients + ? (amount + PositionedStack.CHANCE_FULL - 1) / PositionedStack.CHANCE_FULL + : amount / PositionedStack.CHANCE_FULL; + final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), entryAmount); + + if (amount % PositionedStack.CHANCE_FULL != 0) { + approximate.add(entry.getKey()); + } + + result.add(stack); + } + + return result; + } + + protected static ItemStackAmount getInventoryItems() { + GuiScreen screen = NEIClientUtils.mc().currentScreen; + + if (screen instanceof IGuiContainerOverlay overlay) { + screen = overlay.getFirstScreen(); + } + + if (screen instanceof GuiContainer container) { + return AutoCraftingManager.getInventoryItems(container); + } + + return new ItemStackAmount(); + } + + protected static List subtractItems(ItemStackAmount items, ItemStackAmount subtrahend) { + final List result = new ArrayList<>(); + + for (Map.Entry entry : items.entrySet()) { + final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), 0); + final long amount = entry.getValue() - subtrahend.getOrDefault(stack, 0L); + + if (amount > 0) { + result.add(StackInfo.withAmount(stack, amount)); + } + } + + return result; + } + +} diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 4f53afa2..4e55a7e6 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -667,6 +667,10 @@ nei.recipe.badge.ncp.tooltip=Considered in parallel calculation, but not get con nei.recipe.badge.chance.consume=Consume Chance: %s nei.recipe.badge.chance.output=Output Chance: %s +nei.recipe.items.results=Results +nei.recipe.items.ingredients=Ingredients +nei.recipe.items.ingredients_needed=Ingredients Needed + # Deprecated nei.recipe.fuel.required=%s required # Deprecated