Refactoring Chain Tooltip (#1044)

This commit is contained in:
slprime authored and GitHub committed 2026-09-25 22:08:15 +03:00
1 parent 939845a9ed
commit 0ec87a8b9e
13 files changed
+478 -419

No files matched your search

@@ -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<ItemsTooltipLineHandler> 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<ItemsTooltipLineHandler> 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);
}
}
@@ -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<String> names = new ArrayList<>();
protected int tailCount = 0;
public ItemsListTooltipLineHandler(String label, List<ItemStack> 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);
}
}
@@ -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<ItemStack> items;
protected final List<Long> amounts = new ArrayList<>();
protected final List<String> 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<ItemStack> items, int maxRows) {
return new ItemsTooltipLineHandler(label, items, true, maxRows, false);
}
public static ItemsTooltipLineHandler list(String label, List<ItemStack> items, int maxRows) {
return new ItemsTooltipLineHandler(label, items, true, maxRows, true);
}
public ItemsTooltipLineHandler(String label, List<ItemStack> items) {
this(label, items, true, 5);
this(label, items, true, DEFAULT_MAX_ROWS, false);
}
public ItemsTooltipLineHandler(String label, List<ItemStack> items, boolean saveStackSize, int maxRows) {
this(label, items, saveStackSize, maxRows, false);
}
protected ItemsTooltipLineHandler(String label, List<ItemStack> 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));
}
@@ -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");
}
@@ -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<String, String> handlerTitleCache = new LRUCache<>(100);
private final LRUCache<String, String> 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
@@ -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<Widget> collectHandlers(RecipeChainMath math) {
final Map<String, HandlerStats> handlerStats = new HashMap<>();
final List<Widget> widgets = new ArrayList<>();
final Set<RecipeId> 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();
@@ -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;
}
@@ -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<? extends IRecipeHandler> 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";
@@ -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<ItemsTooltipLineHandler> 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<ItemStack> items, Set<NBTTagCompound> 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<ItemStack> items, Set<NBTTagCompound> 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<ItemStack> items, Set<NBTTagCompound> 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;
@@ -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<NBTTagCompound> missing;
private final Map<ItemStack, Boolean> cache = new IdentityHashMap<>();
public MissingStacksAmountRenderer(Set<NBTTagCompound> 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<NBTTagCompound, HandlerInfo> handlers;
private final Map<ItemStack, HandlerInfo> cache = new IdentityHashMap<>();
private final Map<String, String> titleCache = new HashMap<>();
public HandlerIconRenderer(Map<NBTTagCompound, HandlerInfo> 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<BookmarkItem> initialItems;
protected final Map<RecipeId, Long> 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<ItemStack> available = new ArrayList<>();
final List<ItemStack> inputs = new ArrayList<>();
final List<ItemStack> outputs = new ArrayList<>();
final List<ItemStack> remainder = new ArrayList<>();
final List<ItemStack> craftingNeeded = new ArrayList<>();
final Map<NBTTagCompound, HandlerInfo> 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<BookmarkItem, Long> 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<NBTTagCompound> 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<ItemStack> covered = new ArrayList<>();
for (ItemStack stack : available) {
if (!missing.contains(StackInfo.itemStackToNBT(stack, false))) {
covered.add(stack);
}
}
final Comparator<ItemStack> 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<NBTTagCompound, HandlerInfo> 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<ItemStack> 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);
}
}
@@ -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
@@ -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=В горизонтальном виде
@@ -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=У горизонтальному вигляді