Add Recipe Groups to GuiRecipe (#1049)

This commit is contained in:
slprime authored and GitHub committed 2026-09-27 07:39:27 +03:00
1 parent 3c472a02cb
commit a54c414595
16 files changed
+1464 -840

No files matched your search

@@ -8,6 +8,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -16,6 +17,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
@@ -322,6 +324,21 @@ public class FavoriteRecipes {
return getFavorite(stack) != null;
}
public static void toggleFavoriteItem(ItemStack stack) {
if (stack == null) return;
if (containsManual(stack)) {
setFavorite(stack, null);
} else {
setFavorite(
stack,
RecipeId.of(
stack,
FavoriteRecipes.FAVORITE_ITEM,
Arrays.asList(new ItemStack(Blocks.fire, 1), stack)));
}
}
public static void setFavorite(ItemStack stack, RecipeId recipeId) {
if (stack == null) return;
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.init.Blocks;
@@ -42,7 +43,8 @@ public class PositionedStack implements Cloneable {
protected boolean permutated = false;
protected String acceptsLabel;
protected List<String> tooltip;
protected Function<ItemStack, List<String>> tooltip;
protected List<Badge> badges;
public PositionedStack(Object object, int x, int y, boolean genPerms) {
@@ -115,11 +117,15 @@ public class PositionedStack implements Cloneable {
}
public void setTooltip(List<String> tooltip) {
this.tooltip = tooltip;
this.tooltip = tooltip == null ? null : item -> tooltip;
}
public void setTooltipProvider(Function<ItemStack, List<String>> provider) {
this.tooltip = provider;
}
public List<String> getTooltip() {
return this.tooltip;
return this.tooltip == null ? null : this.tooltip.apply(this.item);
}
public int getChance() {
@@ -135,7 +141,6 @@ public class PositionedStack implements Cloneable {
PositionedStack pStack = (PositionedStack) super.clone();
pStack.items = Arrays.stream(this.items).map(ItemStack::copy).toArray(ItemStack[]::new);
pStack.item = this.item == null ? null : this.item.copy();
pStack.tooltip = this.tooltip == null ? null : new ArrayList<>(this.tooltip);
pStack.badges = this.badges == null ? null : new ArrayList<>(this.badges);
return pStack;
} catch (CloneNotSupportedException e) {
@@ -1,121 +1,242 @@
package codechicken.nei.recipe;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Collections;
import java.util.List;
import net.minecraft.init.Items;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.Item;
import net.minecraft.item.ItemDye;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.RecipeFireworks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.InventoryCraftingDummy;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.guihook.GuiContainerManager;
public class FireworkRecipeHandler extends ShapelessRecipeHandler {
public class CachedFireworkRecipe extends CachedShapelessRecipe {
final LinkedList<Object> itemList = new LinkedList<>();
public final Object[] baseIngredients;
public final Object extraIngred;
public final int recipeType;
public final Object groupId;
public CachedFireworkRecipe(Object[] base, Object extra, int type) {
super(new ItemStack(Items.fireworks));
this.baseIngredients = base;
this.extraIngred = extra;
public CachedFireworkRecipe(List<ItemStack> ingredients, ItemStack result, int type, Object groupId) {
super(ingredients, result);
this.recipeType = type;
cycle();
}
public void cycle() {
itemList.clear();
itemList.addAll(Arrays.asList(baseIngredients));
int extras = (cycleticks / 40) % (10 - itemList.size());
for (int i = 0; i < extras; i++) itemList.add(extraIngred);
setIngredients(itemList);
List<PositionedStack> ingreds = getIngredients();
for (int i = 0; i < 9; i++)
inventoryCrafting.setInventorySlotContents(i, i < ingreds.size() ? ingreds.get(i).item : null);
if (!recipeFireworks.matches(inventoryCrafting, null)) throw new RuntimeException("Invalid Recipe?");
setResult(recipeFireworks.getCraftingResult(null));
this.groupId = groupId;
}
}
// indexed by the "Type" explosion tag
private static final ItemStack[] SHAPES = { null, new ItemStack(Items.fire_charge),
new ItemStack(Items.gold_nugget), new ItemStack(Items.skull, 1, Short.MAX_VALUE),
new ItemStack(Items.feather) };
private final InventoryCrafting inventoryCrafting = new InventoryCraftingDummy();
private final RecipeFireworks recipeFireworks = new RecipeFireworks();
public final ArrayList<CachedFireworkRecipe> mfireworks = new ArrayList<>();
private ItemStack usageIngredient = null;
public FireworkRecipeHandler() {
super();
stackorder = new int[][] { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 }, { 0, 2 }, { 1, 2 },
{ 2, 2 } };
loadAllFireworks();
}
private void loadAllFireworks() {
// charges
Item[] shapes = new Item[] { null, Items.fire_charge, Items.gold_nugget, Items.feather, Items.skull };
Item[] effects = new Item[] { null, Items.diamond, Items.glowstone_dust };
for (Item shape : shapes)
for (Item effect : effects) genRecipe(Items.gunpowder, shape, effect, Items.dye, Items.dye, 0);
// fireworks
genRecipe(Items.gunpowder, Items.paper, Items.firework_charge, 2);
genRecipe(Items.gunpowder, Items.gunpowder, Items.paper, Items.firework_charge, 2);
genRecipe(Items.gunpowder, Items.gunpowder, Items.gunpowder, Items.paper, Items.firework_charge, 2);
// setup a valid charge to use for the recolour recipe
for (int i = 0; i < 9; i++) inventoryCrafting.setInventorySlotContents(i, null);
inventoryCrafting.setInventorySlotContents(0, new ItemStack(Items.gunpowder));
inventoryCrafting.setInventorySlotContents(1, new ItemStack(Items.dye));
recipeFireworks.matches(inventoryCrafting, null);
ItemStack charge = recipeFireworks.getCraftingResult(null);
genRecipe(charge, Items.dye, Items.dye, 1);
@Override
public Object getRecipeGroupId(int recipe) {
return this.arecipes.get(recipe) instanceof CachedFireworkRecipe firework ? firework.groupId : null;
}
private void genRecipe(Object... params) {
int numIngreds = 0;
for (int i = 0; i < params.length - 2; i++) if (params[i] != null) numIngreds++;
private void loadChargeRecipes() {
final List<List<ItemStack>> dyeSets = new ArrayList<>();
for (int i = 0; i < params.length - 1; i++)
if (params[i] instanceof Item) params[i] = new ItemStack((Item) params[i], 1, Short.MAX_VALUE);
for (int dye = 0; dye < ItemDye.field_150922_c.length; dye++) {
dyeSets.add(Collections.singletonList(new ItemStack(Items.dye, 1, dye)));
}
Object[] ingreds = new Object[numIngreds];
for (int i = 0, j = 0; i < params.length - 2; i++) if (params[i] != null) ingreds[j++] = params[i];
for (ItemStack shape : SHAPES) {
for (int effect = 0; effect < 4; effect++) {
addRecipes(getChargeBase(shape, (effect & 1) != 0, (effect & 2) != 0), dyeSets, 0);
}
}
mfireworks
.add(new CachedFireworkRecipe(ingreds, params[params.length - 2], (Integer) params[params.length - 1]));
final ItemStack charge = craft(Arrays.asList(new ItemStack(Items.gunpowder), new ItemStack(Items.dye)));
addRecipes(Collections.singletonList(charge), dyeSets, 1);
}
private void loadRocketRecipes() {
final ItemStack charge = craft(Arrays.asList(new ItemStack(Items.gunpowder), new ItemStack(Items.dye)));
final List<List<ItemStack>> chargeSets = new ArrayList<>();
for (int count = 0; count < 9; count++) {
chargeSets.add(Collections.nCopies(count, charge));
}
for (int flight = 1; flight <= 3; flight++) {
addRecipes(getRocketBase(flight), chargeSets, 2);
}
}
private void loadExactChargeRecipes(NBTTagCompound explosion) {
final byte type = explosion.getByte("Type");
if (explosion.hasKey("FadeColors")) {
final NBTTagCompound baseTag = new NBTTagCompound();
final ItemStack charge = new ItemStack(Items.firework_charge);
baseTag.setTag("Explosion", explosion.copy());
baseTag.getCompoundTag("Explosion").removeTag("FadeColors");
charge.setTagCompound(baseTag);
addRecipes(Collections.singletonList(charge), getDyeSets(explosion.getIntArray("FadeColors")), 1);
} else if (type >= 0 && type < SHAPES.length) {
addRecipes(
getChargeBase(SHAPES[type], explosion.getBoolean("Flicker"), explosion.getBoolean("Trail")),
getDyeSets(explosion.getIntArray("Colors")),
0);
}
}
private void loadExactRocketRecipes(NBTTagCompound fireworks) {
final NBTTagList explosions = fireworks.getTagList("Explosions", 10);
final List<ItemStack> charges = new ArrayList<>();
for (int i = 0; i < explosions.tagCount(); i++) {
final ItemStack charge = new ItemStack(Items.firework_charge);
charge.setTagCompound(new NBTTagCompound());
charge.getTagCompound().setTag("Explosion", explosions.getCompoundTagAt(i));
charges.add(charge);
}
addRecipes(getRocketBase(fireworks.getByte("Flight")), Collections.singletonList(charges), 2);
}
private static List<ItemStack> getChargeBase(ItemStack shape, boolean flicker, boolean trail) {
final List<ItemStack> base = new ArrayList<>();
base.add(new ItemStack(Items.gunpowder));
if (shape != null) {
base.add(shape);
}
if (flicker) {
base.add(new ItemStack(Items.glowstone_dust));
}
if (trail) {
base.add(new ItemStack(Items.diamond));
}
return base;
}
private static List<ItemStack> getRocketBase(int flight) {
final List<ItemStack> base = new ArrayList<>();
for (int i = 0; i < flight; i++) {
base.add(new ItemStack(Items.gunpowder));
}
base.add(new ItemStack(Items.paper));
return base;
}
private static List<List<ItemStack>> getDyeSets(int[] colors) {
final List<ItemStack> dyes = new ArrayList<>();
for (int color : colors) {
for (int dye = 0; dye < ItemDye.field_150922_c.length; dye++) {
if (ItemDye.field_150922_c[dye] == color) {
dyes.add(new ItemStack(Items.dye, 1, dye));
break;
}
}
}
if (dyes.isEmpty() || dyes.size() != colors.length) {
return Collections.emptyList();
}
if (Arrays.stream(colors).distinct().count() > 1) {
return Collections.singletonList(dyes);
}
final List<List<ItemStack>> dyeSets = new ArrayList<>();
for (int count = 1; count < 9; count++) {
dyeSets.add(Collections.nCopies(count, dyes.get(0)));
}
return dyeSets;
}
private void addRecipes(List<ItemStack> base, List<List<ItemStack>> extraSets, int type) {
final Object groupId = new Object();
for (List<ItemStack> extras : extraSets) {
final List<ItemStack> ingredients = new ArrayList<>(base);
ingredients.addAll(extras);
final ItemStack result = craft(ingredients);
if (result != null) {
final CachedFireworkRecipe recipe = new CachedFireworkRecipe(ingredients, result, type, groupId);
if (usageIngredient == null || recipe.contains(recipe.ingredients, usageIngredient)) {
arecipes.add(recipe);
}
}
}
}
private ItemStack craft(List<ItemStack> ingredients) {
if (ingredients.size() > 9) {
return null;
}
for (int i = 0; i < 9; i++) {
inventoryCrafting.setInventorySlotContents(i, i < ingredients.size() ? ingredients.get(i) : null);
}
return recipeFireworks.matches(inventoryCrafting, null) ? recipeFireworks.getCraftingResult(inventoryCrafting)
: null;
}
@Override
public void loadCraftingRecipes(ItemStack result) {
for (CachedFireworkRecipe recipe : mfireworks) {
if (recipe.result.item.getItem() == result.getItem()) {
recipe.cycle();
arecipes.add(recipe);
final NBTTagCompound tag = result.getTagCompound();
if (result.getItem() == Items.firework_charge) {
if (tag != null) {
loadExactChargeRecipes(tag.getCompoundTag("Explosion"));
}
if (arecipes.isEmpty()) {
loadChargeRecipes();
}
} else if (result.getItem() == Items.fireworks) {
if (tag != null) {
loadExactRocketRecipes(tag.getCompoundTag("Fireworks"));
}
if (arecipes.isEmpty()) {
loadRocketRecipes();
}
}
// show random recolouring recipes as well
}
@Override
public void loadCraftingRecipes(String outputId, Object... results) {
if (outputId.equals("crafting") && getClass() == FireworkRecipeHandler.class) {
arecipes.addAll(mfireworks);
loadChargeRecipes();
loadRocketRecipes();
} else {
super.loadCraftingRecipes(outputId, results);
}
@@ -123,20 +244,9 @@ public class FireworkRecipeHandler extends ShapelessRecipeHandler {
@Override
public void loadUsageRecipes(ItemStack ingredient) {
for (CachedFireworkRecipe recipe : mfireworks) {
if (recipe.contains(recipe.ingredients, ingredient)) {
recipe.cycle();
arecipes.add(recipe);
}
}
}
@Override
public void onUpdate() {
if (!NEIClientUtils.shiftKey()) {
cycleticks++;
if (cycleticks % 20 == 0) for (CachedRecipe crecipe : arecipes) ((CachedFireworkRecipe) crecipe).cycle();
}
usageIngredient = ingredient;
loadChargeRecipes();
loadRocketRecipes();
}
@Override
@@ -147,11 +257,8 @@ public class FireworkRecipeHandler extends ShapelessRecipeHandler {
@Override
public List<String> handleTooltip(GuiRecipe<?> gui, List<String> currenttip, int recipe) {
currenttip = super.handleTooltip(gui, currenttip, recipe);
Point mousepos = GuiDraw.getMousePosition();
Point relMouse = new Point(mousepos.x - gui.guiLeft, mousepos.y - gui.guiTop);
Point recipepos = gui.getRecipePosition(recipe);
if (currenttip.isEmpty() && GuiContainerManager.getStackMouseOver(gui) == null
&& new Rectangle(recipepos.x, recipepos.y, 166, 55).contains(relMouse))
&& new Rectangle(0, 0, 166, 55).contains(gui.getRecipeMousePosition(recipe)))
currenttip.add(
NEIClientUtils.translate(
"recipe.firework.tooltip" + ((CachedFireworkRecipe) arecipes.get(recipe)).recipeType));
@@ -52,6 +52,7 @@ import codechicken.nei.guihook.IContainerTooltipHandler;
import codechicken.nei.guihook.IGuiClientSide;
import codechicken.nei.guihook.IGuiHandleMouseWheel;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollBar;
import codechicken.nei.scroll.ScrollBar.OverflowType;
import codechicken.nei.scroll.ScrollBar.ScrollPlace;
@@ -523,7 +524,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
public int openTargetRecipe(RecipeId recipeId) {
int refIndex = -1;
int recipeIndex = -1;
int recipetype = 0;
this.recipeId = recipeId;
@@ -537,10 +538,9 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
recipetype = j;
if (!this.recipeId.getIngredients().isEmpty()) {
refIndex = SearchRecipeHandler.findFirst(
recipeIndex = SearchRecipeHandler.findFirst(
localHandler,
recipeIndex -> this.recipeId
.equalsIngredients(localHandler.getIngredientStacks(recipeIndex)));
ri -> this.recipeId.equalsIngredients(localHandler.getIngredientStacks(ri)));
}
break;
@@ -549,17 +549,17 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
setRecipePage(recipetype);
this.handlerPages.gotoRefIndex(Math.max(0, refIndex));
this.handlerPages.gotoRecipeIndex(Math.max(0, recipeIndex));
return refIndex;
return recipeIndex;
}
public Recipe getFocusedRecipe() {
final Point mouse = GuiDraw.getMousePosition();
final Widget activeWidget = this.container.getWidgetUnderMouse(mouse.x, mouse.y);
if (activeWidget instanceof NEIRecipeWidget recipeWidget && recipeWidget.isFocusedRecipe(mouse.x, mouse.y)) {
return recipeWidget.getRecipe();
if (activeWidget instanceof RecipeWidget recipeWidget && recipeWidget.isFocusedRecipe(mouse.x, mouse.y)) {
return Recipe.of(recipeWidget.getRecipeHandlerRef());
}
return null;
@@ -569,7 +569,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final Point mouse = GuiDraw.getMousePosition();
final Widget activeWidget = this.container.getWidgetUnderMouse(mouse.x, mouse.y);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
final PositionedStack hovered = recipeWidget.getPositionedStackMouseOver(mouse.x, mouse.y);
if (hovered != null) {
@@ -586,7 +586,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final EmptyContainer slotcontainer = (EmptyContainer) inventorySlots;
slotcontainer.setActiveStack(null);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
final PositionedStack hovered = recipeWidget.getPositionedStackMouseOver(mousex, mousey);
if (hovered != null) {
@@ -844,7 +844,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
List<String> currenttip) {
final Widget activeWidget = this.container.getWidgetUnderMouse(mousex, mousey);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
currenttip = recipeWidget.handleItemTooltip(itemstack, mousex, mousey, currenttip);
}
@@ -891,7 +891,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
this.recipetype = this.currenthandlers.indexOf(this.handler.original);
setRecipePage(this.recipetype);
this.handlerPages.changePage(currentPage);
this.handlerPages.changePage(currentPage - this.handlerPages.getCurrentPageIndex());
refreshContainer();
}
@@ -1014,8 +1014,23 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
return this.firstGuiGeneral;
}
public Point getRecipePosition(int recipe) {
return getRefIndexPosition(getRecipeIndices().indexOf(recipe));
public Point getRecipePosition(int recipeIndex) {
for (Widget widget : this.container.getWidgets()) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
return new Point(
recipeWidget.x - this.guiLeft,
recipeWidget.y - this.guiTop + recipeWidget.getHandlerInfo().getYShift());
}
}
return new Point(0, 0);
}
public Point getRecipeMousePosition(int recipeIndex) {
final Point mouse = GuiDraw.getMousePosition();
final Point recipePosition = getRecipePosition(recipeIndex);
return new Point(mouse.x - this.guiLeft - recipePosition.x, mouse.y - this.guiTop - recipePosition.y);
}
protected Point getRefIndexPosition(int refIndex) {
@@ -1027,7 +1042,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final List<Widget> children = this.container.getWidgets();
if (refIndex >= 0 && refIndex < children.size()
&& children.get(refIndex) instanceof NEIRecipeWidget recipeWidget) {
&& children.get(refIndex) instanceof RecipeWidget recipeWidget) {
return new Point(
recipeWidget.x - this.guiLeft,
recipeWidget.y - this.guiTop + recipeWidget.getHandlerInfo().getYShift());
@@ -15,12 +15,12 @@ public abstract class GuiRecipeButton extends GuiNEIButton {
public static class UpdateRecipeButtonsEvent extends GuiScreenEvent {
public List<GuiRecipeButton> buttonList;
public NEIRecipeWidget recipeWidget;
public RecipeHandlerRef handlerRef;
public UpdateRecipeButtonsEvent(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget,
public UpdateRecipeButtonsEvent(GuiRecipe<?> gui, RecipeHandlerRef handlerRef,
List<GuiRecipeButton> buttonList) {
super(gui);
this.recipeWidget = recipeWidget;
this.handlerRef = handlerRef;
this.buttonList = new ArrayList<>(buttonList);
}
@@ -29,16 +29,16 @@ public abstract class GuiRecipeButton extends GuiNEIButton {
public HandlerInfo handlerInfo;
public Pre(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget, HandlerInfo handlerInfo) {
super(gui, recipeWidget, new ArrayList<>());
public Pre(GuiRecipe<?> gui, RecipeHandlerRef handlerRef, HandlerInfo handlerInfo) {
super(gui, handlerRef, new ArrayList<>());
this.handlerInfo = handlerInfo;
}
}
public static class Post extends UpdateRecipeButtonsEvent {
public Post(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget, List<GuiRecipeButton> buttonList) {
super(gui, recipeWidget, buttonList);
public Post(GuiRecipe<?> gui, RecipeHandlerRef handlerRef, List<GuiRecipeButton> buttonList) {
super(gui, handlerRef, buttonList);
}
}
}
@@ -91,6 +91,14 @@ public interface IRecipeHandler {
*/
PositionedStack getResultStack(int recipe);
/**
* @param recipe The recipe index to get the group id for.
* @return An id shared by every recipe in this recipe's group, or {@code null} if this recipe isn't grouped.
*/
default Object getRecipeGroupId(int recipe) {
return null;
}
/**
* A tick function called for updating progress bars and cycling damage items.
*/
@@ -1,688 +0,0 @@
package codechicken.nei.recipe;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.KeyManager;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.NEIServerUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.Widget;
import codechicken.nei.api.IGuiContainerOverlay;
import codechicken.nei.api.ShortcutInputHandler;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.GuiRecipeButton.UpdateRecipeButtonsEvent;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.debug.DebugHandlerWidget;
import codechicken.nei.util.NEIMouseUtils;
public class NEIRecipeWidget extends Widget {
protected AcceptsFollowingTooltipLineHandler acceptsFollowingTooltipLineHandler;
protected final Map<PositionedStack, Integer> favoriteIndexes = new WeakHashMap<>();
protected final Map<PositionedStack, List<ItemStack>> permutations = new WeakHashMap<>();
protected final Map<PositionedStack, List<Badge>> badgeCache = new WeakHashMap<>();
protected int favoriteRevision = -1;
protected boolean update = true;
protected int cycleticks = 0;
protected int lastcycle = -1;
protected final RecipeHandlerRef handlerRef;
protected final HandlerInfo handlerInfo;
protected boolean showAsWidget = false;
protected List<GuiRecipeButton> recipeButtons = null;
public NEIRecipeWidget(RecipeHandlerRef handlerRef) {
this.handlerRef = handlerRef;
this.handlerInfo = GuiRecipeTab.getHandlerInfo(this.handlerRef.handler);
update();
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
public void update() {
if (this.showAsWidget || !this.handlerInfo.isAllowOverflowY()) {
final int recipeHeight = this.handlerRef.handler.getRecipeHeight(this.handlerRef.recipeIndex);
this.w = Math.max(166, this.handlerInfo.getWidth());
this.h = recipeHeight > 0 ? recipeHeight : this.handlerInfo.getHeight();
this.h += this.handlerInfo.getYShift();
}
this.update = true;
}
public List<GuiRecipeButton> getRecipeButtons() {
if (this.recipeButtons == null) {
if (!getOutputs().isEmpty()) {
this.recipeButtons = initButtons();
} else {
this.recipeButtons = Collections.emptyList();
}
}
return Collections.unmodifiableList(this.recipeButtons);
}
protected List<GuiRecipeButton> getRecipeButtonsIfInit() {
if (this.recipeButtons == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(this.recipeButtons);
}
public RecipeHandlerRef getRecipeHandlerRef() {
return this.handlerRef;
}
public Recipe getRecipe() {
return Recipe.of(this.handlerRef);
}
public HandlerInfo getHandlerInfo() {
return this.handlerInfo;
}
public void showAsWidget(boolean show) {
this.showAsWidget = show;
}
protected List<GuiRecipeButton> initButtons() {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe != null) {
final UpdateRecipeButtonsEvent.Pre preEvent = new UpdateRecipeButtonsEvent.Pre(
guiRecipe,
this,
this.handlerInfo);
if (MinecraftForge.EVENT_BUS.post(preEvent)) {
return preEvent.buttonList;
} else {
final UpdateRecipeButtonsEvent.Post postEvent = new UpdateRecipeButtonsEvent.Post(
guiRecipe,
this,
getDefaultButtons());
MinecraftForge.EVENT_BUS.post(postEvent);
return postEvent.buttonList;
}
} else {
return getDefaultButtons();
}
}
protected List<GuiRecipeButton> getDefaultButtons() {
GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
final List<GuiRecipeButton> buttons = new ArrayList<>();
final boolean showFavorites = NEIClientConfig.favoritesEnabled() && handlerInfo.getShowFavoritesButton();
final boolean showOverlay = handlerInfo.getShowOverlayButton();
final int x = Math.min(166, this.w) - GuiRecipeButton.BUTTON_WIDTH;
int y = this.h - GuiRecipeButton.BUTTON_HEIGHT - 6;
if (guiContainer instanceof IGuiContainerOverlay overlay) {
guiContainer = overlay.getFirstScreen();
}
if (showOverlay) {
buttons.add(new GuiOverlayButton(guiContainer, this.handlerRef, x, y));
y -= GuiRecipeButton.BUTTON_HEIGHT + 1;
}
if (showFavorites) {
buttons.add(new GuiFavoriteButton(handlerRef, x, y));
}
return buttons;
}
@Override
public void draw(int mouseX, int mouseY) {
final int yShift = this.handlerInfo.getYShift();
if (this.update) {
this.update = false;
if (!NEIClientUtils.shiftKey() && ((this.cycleticks++) / 20) != this.lastcycle || this.lastcycle == -1) {
this.lastcycle = this.cycleticks / 20;
updatePermutations();
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.onPermutationsChanged();
}
}
for (GuiRecipeButton button : getRecipeButtons()) {
button.update();
}
}
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
GL11.glTranslatef(this.x, this.y + yShift, 0);
this.handlerRef.handler.drawBackground(this.handlerRef.recipeIndex);
RenderHelper.enableGUIStandardItemLighting();
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
GL11.glColor4f(1, 1, 1, 1);
GuiContainerManager.enableMatrixStackLogging();
for (PositionedStack pStack : getInputs()) {
if (!this.permutations.containsKey(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getCatalysts()) {
if (!this.permutations.containsKey(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getOutputs()) {
drawItem(pStack, mouseX, mouseY, yShift, false);
}
GuiContainerManager.disableMatrixStackLogging();
this.handlerRef.handler.drawForeground(this.handlerRef.recipeIndex);
final GuiRecipeButton overlayButton = forEachButtons(
button -> (this.showAsWidget ? button instanceof GuiOverlayButton : button.contains(mouseX, mouseY))
? button
: null,
null);
if (overlayButton != null) {
NEIClientUtils.gl2DRenderContext(() -> overlayButton.drawItemOverlay());
}
GL11.glTranslatef(-this.x, -this.y - yShift, 0);
GL11.glPopAttrib();
if (!this.showAsWidget) {
final Minecraft mc = NEIClientUtils.mc();
for (GuiRecipeButton button : getRecipeButtons()) {
button.xPosition += this.x;
button.yPosition += this.y;
button.drawButton(mc, mouseX, mouseY);
button.xPosition -= this.x;
button.yPosition -= this.y;
}
}
DebugHandlerWidget.instance.drawGuiPlaceholder(this);
}
protected void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) {
pStack.draw(mouseX - this.x, mouseY - this.y - yShift);
if (this.handlerInfo.getShowBadge()) {
drawBadge(pStack, input);
}
if (pStack.contains(mouseX - this.x, mouseY - this.y - yShift)) {
NEIClientUtils.gl2DRenderContext(
() -> GuiDraw.drawRect(pStack.relx, pStack.rely, pStack.width, pStack.height, 0x80FFFFFF));
}
}
protected List<Badge> getBadges(PositionedStack pStack, boolean input) {
return this.badgeCache.computeIfAbsent(pStack, k -> {
final List<Badge> badges = k.getBadges();
if (badges == null) {
if (input) {
if (StackInfo.getAmount(pStack.item) == 0) {
return Collections.singletonList(Badge.notConsumed());
} else if (pStack.getChance() == 0) {
return Collections.singletonList(Badge.notConsumedParallel());
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.consumeChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.outputChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
return Collections.emptyList();
}
return badges;
});
}
protected void drawBadge(PositionedStack pStack, boolean input) {
final List<Badge> badges = getBadges(pStack, input);
for (Badge badge : badges) {
badge.draw(new Rectangle4i(pStack.relx, pStack.rely, pStack.width, pStack.height));
}
}
@Override
public boolean handleKeyPress(int keyID, char keyChar) {
if (NEIClientConfig.favoritesEnabled()
&& KeyManager.isHashDown("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH)) {
final Point mouse = GuiDraw.getMousePosition();
final int yShift = this.handlerInfo.getYShift();
PositionedStack pStackOver = null;
for (PositionedStack pStack : getInputs()) {
if (pStack.contains(mouse.x - this.x, mouse.y - this.y - yShift)) {
pStackOver = pStack;
break;
}
}
if (pStackOver != null) {
if (FavoriteRecipes.containsManual(pStackOver.item)) {
FavoriteRecipes.setFavorite(pStackOver.item, null);
} else {
FavoriteRecipes.setFavorite(
pStackOver.item,
RecipeId.of(
pStackOver.item,
FavoriteRecipes.FAVORITE_ITEM,
Arrays.asList(new ItemStack(Blocks.fire, 1), pStackOver.item)));
}
return true;
}
}
return false;
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
final Point mouse = GuiDraw.getMousePosition();
forEachButtons(button -> {
if (button.contains(mouse.x, mouse.y)) {
button.lastKeyTyped(keyChar, keyID);
}
return null;
}, null);
}
@Override
public boolean handleClick(int mouseX, int mouseY, int button) {
if (ShortcutInputHandler.handleMouseClick(getStackMouseOver(mouseX, mouseY))) {
return true;
}
if (button == 0) {
final Minecraft mc = NEIClientUtils.mc();
if (forEachButtons(guibutton -> {
if (guibutton.mousePressed(mc, mouseX, mouseY)) {
NEIClientUtils.playClickSound();
guibutton.mouseReleased(mouseX, mouseY);
return true;
}
return null;
}, false)) {
return true;
}
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return false;
}
return this.handlerRef.handler.mouseClicked(guiRecipe, button, this.handlerRef.recipeIndex);
}
@Override
public List<String> handleTooltip(int mouseX, int mouseY, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
final List<String> localTooltip = this.handlerRef.handler
.handleTooltip(guiRecipe, tooltip, this.handlerRef.recipeIndex);
return forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleTooltip(localTooltip) : null,
localTooltip);
}
@Override
public List<String> handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
tooltip = this.handlerRef.handler.handleItemTooltip(guiRecipe, itemstack, tooltip, this.handlerRef.recipeIndex);
if (itemstack != null) {
final PositionedStack hovered = getPositionedStackMouseOver(mousex, mousey);
if (hovered != null) {
if (this.handlerInfo.getShowBadge()) {
final List<Badge> badges = getBadges(hovered, getOutputs().indexOf(hovered) == -1);
for (Badge badge : badges) {
if (badge.getTooltip() != null && !badge.getTooltip().isEmpty()) {
tooltip.addAll(badge.getTooltip());
}
}
}
final List<String> customTooltip = hovered.getTooltip();
if (customTooltip != null && !customTooltip.isEmpty()) {
tooltip.addAll(customTooltip);
}
}
if (hovered == null || !NEIClientConfig.showCycledIngredientsTooltip()
|| this.permutations.getOrDefault(hovered, Collections.emptyList()).size() <= 1
|| getOutputs().indexOf(hovered) != -1) {
this.acceptsFollowingTooltipLineHandler = null;
} else if (this.acceptsFollowingTooltipLineHandler == null
|| this.acceptsFollowingTooltipLineHandler.tooltipGUID != hovered) {
this.acceptsFollowingTooltipLineHandler = AcceptsFollowingTooltipLineHandler
.of(hovered, this.permutations.get(hovered), hovered.item);
if (this.acceptsFollowingTooltipLineHandler != null && hovered.getAcceptsLabel() != null) {
this.acceptsFollowingTooltipLineHandler.setLabel(hovered.getAcceptsLabel());
}
}
} else if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler = null;
}
if (this.acceptsFollowingTooltipLineHandler != null) {
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.acceptsFollowingTooltipLineHandler));
}
return tooltip;
}
@Override
public Map<String, String> handleHotkeys(int mouseX, int mouseY, Map<String, String> hotkeys) {
final int yShift = this.handlerInfo.getYShift();
final Map<String, String> buttonHotkeys = forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleHotkeys(mouseX, mouseY, hotkeys) : null,
hotkeys);
if (this.acceptsFollowingTooltipLineHandler != null) {
buttonHotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH,
NEIMouseUtils.MOUSE_BTN_NONE + NEIMouseUtils.MOUSE_SCROLL),
NEIClientUtils.translate("recipe.accepts.scroll"));
}
if (getInputs().stream().anyMatch(
pStack -> this.permutations.getOrDefault(pStack, Collections.emptyList()).size() > 1
&& pStack.contains(mouseX - this.x, mouseY - this.y - yShift))) {
hotkeys.put(
KeyManager.getKeyName("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH),
NEIClientUtils.translate("recipe.favorite.toggle"));
}
return buttonHotkeys;
}
@Override
public boolean onMouseWheel(int scroll, int mx, int my) {
if (forEachButtons(button -> button.contains(mx, my) ? button.mouseScrolled(scroll) : null, false)) {
return true;
}
if (scrollPermutations(scroll, mx, my)) {
return true;
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
return guiRecipe != null
&& this.handlerRef.handler.mouseScrolled(guiRecipe, scroll, this.handlerRef.recipeIndex);
}
protected boolean scrollPermutations(int scroll, int mx, int my) {
if (!NEIClientUtils.shiftKey()) return false;
final PositionedStack overStack = getPositionedStackMouseOver(mx, my);
if (overStack != null && this.permutations.getOrDefault(overStack, Collections.emptyList()).size() > 1) {
final List<ItemStack> items = this.permutations.get(overStack);
final int stackIndex = indexOf(items, overStack.item);
final ItemStack stack = items.get((items.size() - scroll + stackIndex) % items.size());
Stream.concat(getInputs().stream(), getCatalysts().stream()).filter(pStack -> pStack.containsWithNBT(stack))
.forEach(pStack -> pStack.setPermutationToRender(stack));
if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler
.setActiveStack(((PositionedStack) this.acceptsFollowingTooltipLineHandler.tooltipGUID).item);
}
return true;
}
return false;
}
protected int indexOf(List<ItemStack> list, ItemStack stack) {
for (int i = 0; i < list.size(); i++) {
if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(list.get(i), stack)) {
return i;
}
}
return -1;
}
protected <R> R forEachButtons(Function<GuiRecipeButton, R> callback, R defaultValue) {
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.xPosition += this.x;
button.yPosition += this.y;
final R result = callback.apply(button);
button.xPosition -= this.x;
button.yPosition -= this.y;
if (result != null) {
return result;
}
}
return defaultValue;
}
public boolean isFocusedRecipe(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStackOver : getOutputs()) {
if (pStackOver.contains(mx - this.x, my - this.y - yShift)) {
return true;
}
}
return false;
}
public PositionedStack getPositionedStackMouseOver(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStack : getInputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
for (PositionedStack pStack : getCatalysts()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
for (PositionedStack pStack : getOutputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
return null;
}
@Override
public ItemStack getStackMouseOver(int mx, int my) {
final PositionedStack pStack = getPositionedStackMouseOver(mx, my);
return pStack != null ? pStack.item : null;
}
protected GuiRecipe<?> getGuiRecipe() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
return guiRecipe;
}
return null;
}
protected GuiContainer getFirstGui() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
return guiRecipe.firstGui;
}
return guiContainer;
}
protected void updatePermutations() {
final int rev = FavoriteRecipes.getRevision();
if (this.favoriteRevision != rev) {
this.favoriteRevision = rev;
this.acceptsFollowingTooltipLineHandler = null;
this.favoriteIndexes.clear();
this.permutations.clear();
this.badgeCache.clear();
}
for (PositionedStack pStack : getInputs()) {
updatePermutationsFor(pStack);
}
for (PositionedStack pStack : getCatalysts()) {
updatePermutationsFor(pStack);
}
if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler
.setActiveStack(((PositionedStack) this.acceptsFollowingTooltipLineHandler.tooltipGUID).item);
}
}
private void updatePermutationsFor(PositionedStack pStack) {
List<ItemStack> perms = this.permutations.get(pStack);
int favoriteIndex = this.favoriteIndexes.getOrDefault(pStack, -1);
boolean needsUpdate = false;
if (perms == null) {
perms = pStack.getFilteredPermutations();
favoriteIndex = -1;
if (perms.size() > 1) {
for (ItemStack item : perms) {
if (!FavoriteRecipes.containsManual(item)) break;
favoriteIndex++;
}
}
this.permutations.put(pStack, perms);
this.favoriteIndexes.put(pStack, favoriteIndex);
needsUpdate = true;
}
if (perms.size() > 1 || needsUpdate) {
final int size = favoriteIndex != -1 ? favoriteIndex + 1 : perms.size();
if (size > 0) {
pStack.setPermutationToRender(perms.get(this.lastcycle % size));
}
}
}
protected List<PositionedStack> getInputs() {
return this.handlerRef.handler.getIngredientStacks(this.handlerRef.recipeIndex);
}
protected List<PositionedStack> getOutputs() {
final PositionedStack pStackResult = this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex);
return pStackResult != null ? Arrays.asList(pStackResult)
: this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex);
}
protected List<PositionedStack> getCatalysts() {
if (this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex) == null) {
return Collections.emptyList();
}
return this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex);
}
}
@@ -36,12 +36,12 @@ public class RecipeHandlerRef {
.getCraftingHandlers("recipeId", recipeId.getResult(), recipeId);
for (ICraftingHandler handler : handlers) {
int refIndex = SearchRecipeHandler.findFirst(
int recipeIndex = SearchRecipeHandler.findFirst(
handler,
recipeIndex -> recipeId.equalsIngredients(handler.getIngredientStacks(recipeIndex)));
index -> recipeId.equalsIngredients(handler.getIngredientStacks(index)));
if (refIndex >= 0) {
return new RecipeHandlerRef(handler, refIndex);
if (recipeIndex >= 0) {
return new RecipeHandlerRef(handler, recipeIndex);
}
}
@@ -66,10 +66,6 @@ public class RecipeHandlerRef {
return firstGui != null && firstGui.inventorySlots != null ? firstGui : null;
}
public NEIRecipeWidget getRecipeWidget() {
return new NEIRecipeWidget(this);
}
public IOverlayHandler getOverlayHandler(GuiContainer gui) {
final GuiContainer firstGui = prepareGui(gui);
@@ -2,10 +2,13 @@ package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.Widget;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollContainer;
public class RecipePageManager {
@@ -16,7 +19,6 @@ public class RecipePageManager {
private String cacheKey = "";
private int currentPageIndex = -1;
private List<Integer> recipeIndices = new ArrayList<>();
private List<List<Widget>> pageWidgets = new ArrayList<>();
public RecipePageManager(SearchRecipeHandler<?> handler, HandlerInfo handlerInfo, ScrollContainer container) {
@@ -52,12 +54,28 @@ public class RecipePageManager {
final int width = Math.max(166, this.handlerInfo.getWidth());
final List<Integer> searchRecipes = this.handler.getFilteredRecipes();
final int numRecipes = searchRecipes.size();
final List<List<Integer>> groupedRecipes = new ArrayList<>();
final Map<Object, Integer> groupEndIndices = new HashMap<>();
List<Widget> widgets = new ArrayList<>();
int shiftY = 0;
for (int i = 0; i < numRecipes; i++) {
final int recipeIndex = searchRecipes.get(i);
final NEIRecipeWidget widget = RecipeHandlerRef.of(this.handler.original, recipeIndex).getRecipeWidget();
final Object groupId = this.handler.original.getRecipeGroupId(recipeIndex);
final int groupIndex = groupId != null
? groupEndIndices.computeIfAbsent(groupId, k -> groupedRecipes.size())
: groupedRecipes.size();
if (groupedRecipes.size() <= groupIndex) {
groupedRecipes.add(new ArrayList<>());
}
groupedRecipes.get(groupIndex).add(recipeIndex);
}
for (int groupIndex = 0; groupIndex < groupedRecipes.size(); groupIndex++) {
final RecipeWidget widget = new RecipeWidget(this.handler.original, groupedRecipes.get(groupIndex));
widget.w = width;
if (allowOverflowY) {
@@ -109,29 +127,21 @@ public class RecipePageManager {
}
this.currentPageIndex = page;
this.recipeIndices = new ArrayList<>();
for (Widget widget : getCurrentPageWidgets()) {
if (widget instanceof NEIRecipeWidget recipeWidget) {
this.recipeIndices.add(recipeWidget.getRecipeHandlerRef().recipeIndex);
}
}
this.container.setVerticalScrollOffset(0);
}
public void gotoRefIndex(int refIndex) {
int page = 0;
public boolean gotoRecipeIndex(int recipeIndex) {
for (int i = 0; i < this.pageWidgets.size(); i++) {
refIndex -= this.pageWidgets.get(i).size();
if (refIndex < 0) {
page = i;
break;
for (int page = 0; page < this.pageWidgets.size(); page++) {
for (Widget widget : this.pageWidgets.get(page)) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
changePage(page - this.currentPageIndex);
return true;
}
}
}
changePage(page);
return false;
}
public List<Widget> getCurrentPageWidgets() {
@@ -143,7 +153,7 @@ public class RecipePageManager {
int shiftY = 0;
for (Widget widget : currentWidgets) {
if (widget instanceof NEIRecipeWidget recipeWidget) {
if (widget instanceof RecipeWidget recipeWidget) {
recipeWidget.setLocation(2, shiftY);
}
shiftY += widget.h;
@@ -153,7 +163,20 @@ public class RecipePageManager {
}
public List<Integer> getRecipeIndices() {
return this.recipeIndices;
if (this.pageWidgets.isEmpty()) {
return Collections.emptyList();
}
final List<Integer> recipeIndices = new ArrayList<>();
for (Widget widget : this.pageWidgets.get(this.currentPageIndex)) {
if (widget instanceof RecipeWidget recipeWidget) {
recipeIndices.add(recipeWidget.getRecipeHandlerRef().recipeIndex);
}
}
return recipeIndices;
}
}
@@ -12,6 +12,7 @@ import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.GuiHelper;
import codechicken.nei.util.ReadableNumberConverter;
@@ -30,7 +31,7 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
protected String recipeName = "";
protected GuiRecipe<?> gui = null;
protected RecipeHandlerRef handlerRef = null;
protected NEIRecipeWidget widget = null;
protected RecipeWidget widget = null;
protected RecipeId recipeId = null;
protected boolean createdGui = false;
protected long multiplier = 0;
@@ -63,13 +64,13 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
this.handlerRef = RecipeHandlerRef.of(this.recipeId);
this.createdGui = true;
if (handlerRef != null) {
this.widget = new NEIRecipeWidget(this.handlerRef);
if (this.handlerRef != null) {
this.widget = new RecipeWidget(this.handlerRef);
this.widget.showAsWidget(true);
this.widget.x = BG_PADDING;
this.widget.y = BG_PADDING + 12;
this.recipeName = this.widget.getRecipeHandlerRef().handler.getRecipeName().trim();
this.recipeName = this.handlerRef.handler.getRecipeName().trim();
}
}
@@ -52,15 +52,10 @@ class SearchRecipeHandler<H extends IRecipeHandler> {
public static int findFirst(IRecipeHandler handler, IntPredicate predicate) {
final IRecipeFilter filter = searchingAvailable(handler) ? GuiRecipe.getRecipeListFilter() : null;
int refIndex = -1;
for (int recipeIndex = 0; recipeIndex < handler.numRecipes(); recipeIndex++) {
if (filter == null || mathRecipe(handler, recipeIndex, filter)) {
refIndex++;
if (predicate.test(recipeIndex)) {
return refIndex;
}
if ((filter == null || mathRecipe(handler, recipeIndex, filter)) && predicate.test(recipeIndex)) {
return recipeIndex;
}
}
@@ -37,9 +37,9 @@ import codechicken.nei.recipe.GuiRecipeButton;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.HandlerInfo;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.NEIRecipeWidget;
import codechicken.nei.recipe.TemplateRecipeHandler;
import codechicken.nei.recipe.TemplateRecipeHandler.RecipeTransferRect;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollContainer;
public class DebugHandlerWidget extends Widget {
@@ -532,7 +532,7 @@ public class DebugHandlerWidget extends Widget {
}
}
public void drawGuiPlaceholder(NEIRecipeWidget widget) {
public void drawGuiPlaceholder(RecipeWidget widget) {
if (!this.showWidget) return;
NEIClientUtils.gl2DRenderContext(() -> {
@@ -0,0 +1,99 @@
package codechicken.nei.recipe.widget;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import net.minecraft.item.ItemStack;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.NEIServerUtils;
public class PermutationsCycler<K> {
protected final Map<K, List<ItemStack>> permutations;
protected final Map<K, Integer> favoriteCounts;
protected final Function<K, List<ItemStack>> loader;
protected Runnable onInvalidate = null;
protected int favoriteRevision = -1;
public PermutationsCycler(Map<K, List<ItemStack>> storage, Map<K, Integer> favoriteStorage,
Function<K, List<ItemStack>> loader) {
this.permutations = storage;
this.favoriteCounts = favoriteStorage;
this.loader = loader;
}
public PermutationsCycler<K> onInvalidate(Runnable callback) {
this.onInvalidate = callback;
return this;
}
public boolean validate() {
final int rev = FavoriteRecipes.getRevision();
if (this.favoriteRevision != rev) {
this.favoriteRevision = rev;
this.permutations.clear();
this.favoriteCounts.clear();
if (this.onInvalidate != null) {
this.onInvalidate.run();
}
return true;
}
return false;
}
public boolean contains(K key) {
validate();
return this.permutations.containsKey(key);
}
public List<ItemStack> get(K key) {
validate();
List<ItemStack> items = this.permutations.get(key);
if (items == null) {
items = new ArrayList<>(this.loader.apply(key));
items.sort(Comparator.comparing(FavoriteRecipes::containsManual).reversed());
int favoriteCount = 0;
if (items.size() > 1) {
for (ItemStack stack : items) {
if (!FavoriteRecipes.containsManual(stack)) break;
favoriteCount++;
}
}
this.permutations.put(key, items);
this.favoriteCounts.put(key, favoriteCount);
}
return items;
}
public ItemStack getCycled(K key, int cycle) {
final List<ItemStack> items = get(key);
final int favoriteCount = this.favoriteCounts.getOrDefault(key, 0);
final int size = favoriteCount > 0 ? favoriteCount : items.size();
return size > 0 ? items.get(cycle % size) : null;
}
public static int indexOf(List<ItemStack> list, ItemStack stack) {
for (int i = 0; i < list.size(); i++) {
if (NEIServerUtils.areStacksSameType(list.get(i), stack)) {
return i;
}
}
return -1;
}
}
@@ -0,0 +1,210 @@
package codechicken.nei.recipe.widget;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.item.ItemStack;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.PositionedStack;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.HandlerInfo;
import codechicken.nei.recipe.IRecipeHandler;
public class RecipeGroup {
private static final List<Integer> SINGLE_MEMBER = Collections.singletonList(0);
private final IRecipeHandler handler;
private final List<Integer> recipeIndices;
private final HandlerInfo handlerInfo;
private final PermutationsCycler<Point> slotPermutations;
private List<Integer> allowedMembers = null;
public RecipeGroup(IRecipeHandler handler, List<Integer> recipeIndices) {
this.handler = handler;
this.recipeIndices = new ArrayList<>(recipeIndices);
this.handlerInfo = GuiRecipeTab.getHandlerInfo(handler);
this.slotPermutations = new PermutationsCycler<Point>(
new HashMap<>(),
new HashMap<>(),
this::collectSlotPermutations).onInvalidate(() -> this.allowedMembers = null);
}
public int size() {
return this.recipeIndices.size();
}
public IRecipeHandler getHandler() {
return this.handler;
}
public int getRecipeIndex(int member) {
return this.recipeIndices.get(member);
}
public HandlerInfo getHandlerInfo() {
return this.handlerInfo;
}
public boolean contains(int recipeIndex) {
return this.recipeIndices.contains(recipeIndex);
}
public List<ItemStack> getPermutations(Point slotKey) {
return this.slotPermutations.get(slotKey);
}
public List<PositionedStack> getInputs(int member) {
return this.handler.getIngredientStacks(getRecipeIndex(member));
}
public List<PositionedStack> getOutputs(int member) {
final int recipeIndex = getRecipeIndex(member);
final PositionedStack pStackResult = this.handler.getResultStack(recipeIndex);
return pStackResult != null ? Arrays.asList(pStackResult) : this.handler.getOtherStacks(recipeIndex);
}
public List<PositionedStack> getCatalysts(int member) {
final int recipeIndex = getRecipeIndex(member);
if (this.handler.getResultStack(recipeIndex) == null) {
return Collections.emptyList();
}
return this.handler.getOtherStacks(recipeIndex);
}
public List<PositionedStack> getCyclingStacks(int member) {
final List<PositionedStack> stacks = new ArrayList<>(getInputs(member));
stacks.addAll(getCatalysts(member));
return stacks;
}
public Point getSlotKey(PositionedStack pStack) {
return new Point(pStack.relx, pStack.rely);
}
public PositionedStack getSlot(int member, Point slotKey) {
for (PositionedStack pStack : getCyclingStacks(member)) {
if (pStack.relx == slotKey.x && pStack.rely == slotKey.y) {
return pStack;
}
}
return null;
}
public PositionedStack getOutputSlot(int member, Point slotKey) {
for (PositionedStack pStack : getOutputs(member)) {
if (pStack.relx == slotKey.x && pStack.rely == slotKey.y) {
return pStack;
}
}
return null;
}
public List<ItemStack> getOutputVariants(Point slotKey) {
final List<ItemStack> items = new ArrayList<>();
for (int member = 0; member < size(); member++) {
final PositionedStack slot = getOutputSlot(member, slotKey);
if (slot != null && PermutationsCycler.indexOf(items, slot.item) == -1) {
items.add(slot.item);
}
}
return items;
}
public List<Integer> getAllowedMembers() {
this.slotPermutations.validate();
if (size() == 1) {
return SINGLE_MEMBER;
}
if (this.allowedMembers == null) {
final Set<Point> favoriteSlots = new HashSet<>();
for (int member = 0; member < size(); member++) {
for (PositionedStack pStack : getCyclingStacks(member)) {
final Point slotKey = getSlotKey(pStack);
final List<ItemStack> items = getPermutations(slotKey);
if (items.size() > 1 && items.stream().anyMatch(FavoriteRecipes::containsManual)) {
favoriteSlots.add(slotKey);
}
}
}
this.allowedMembers = filterMembers(favoriteSlots);
}
return this.allowedMembers;
}
protected List<Integer> filterMembers(Set<Point> favoriteSlots) {
final List<Integer> indices = new ArrayList<>();
int minMisses = Integer.MAX_VALUE;
for (int member = 0; member < size(); member++) {
final int misses = countMissingFavorites(member, favoriteSlots);
if (misses < minMisses) {
minMisses = misses;
indices.clear();
}
if (misses == minMisses) {
indices.add(member);
}
}
return indices;
}
protected int countMissingFavorites(int member, Set<Point> favoriteSlots) {
int misses = 0;
for (Point slotKey : favoriteSlots) {
final PositionedStack slot = getSlot(member, slotKey);
if (slot != null && slot.getFilteredPermutations().stream().noneMatch(FavoriteRecipes::containsManual)) {
misses++;
}
}
return misses;
}
protected List<ItemStack> collectSlotPermutations(Point slotKey) {
final List<ItemStack> items = new ArrayList<>();
for (int member = 0; member < size(); member++) {
final PositionedStack slot = getSlot(member, slotKey);
if (slot != null) {
for (ItemStack stack : slot.getFilteredPermutations()) {
if (PermutationsCycler.indexOf(items, stack) == -1) {
items.add(stack);
}
}
}
}
return items;
}
}
@@ -0,0 +1,834 @@
package codechicken.nei.recipe.widget;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Function;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.ItemsTooltipLineHandler;
import codechicken.nei.KeyManager;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.Widget;
import codechicken.nei.api.IGuiContainerOverlay;
import codechicken.nei.api.ShortcutInputHandler;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.AcceptsFollowingTooltipLineHandler;
import codechicken.nei.recipe.Badge;
import codechicken.nei.recipe.GuiFavoriteButton;
import codechicken.nei.recipe.GuiOverlayButton;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.GuiRecipeButton;
import codechicken.nei.recipe.GuiRecipeButton.UpdateRecipeButtonsEvent;
import codechicken.nei.recipe.HandlerInfo;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.recipe.debug.DebugHandlerWidget;
import codechicken.nei.util.NEIMouseUtils;
public class RecipeWidget extends Widget {
protected static final int TICKS_PER_CYCLE = 20;
protected final RecipeGroup group;
protected final IRecipeHandler handler;
protected final HandlerInfo handlerInfo;
protected final PermutationsCycler<PositionedStack> renderPermutations;
protected final Map<PositionedStack, List<Badge>> badgeCache = new WeakHashMap<>();
protected final Map<Integer, List<GuiRecipeButton>> buttonCache = new HashMap<>();
protected AcceptsFollowingTooltipLineHandler acceptsTooltip;
protected ItemsTooltipLineHandler variantsTooltip;
protected Point variantsSlotKey;
protected int activeMember = 0;
protected int memberCursor = -1;
protected int slotCycle = 0;
protected int cycleticks = 0;
protected int lastcycle = -1;
protected boolean showAsWidget = false;
protected boolean update = true;
public RecipeWidget(RecipeHandlerRef handlerRef) {
this(handlerRef.handler, Collections.singletonList(handlerRef.recipeIndex));
}
public RecipeWidget(IRecipeHandler handler, List<Integer> recipeIndices) {
this(new RecipeGroup(handler, recipeIndices));
}
public RecipeWidget(RecipeGroup group) {
this.group = group;
this.handler = group.getHandler();
this.handlerInfo = group.getHandlerInfo();
this.renderPermutations = new PermutationsCycler<PositionedStack>(
new WeakHashMap<>(),
new WeakHashMap<>(),
PositionedStack::getFilteredPermutations).onInvalidate(() -> {
this.badgeCache.clear();
this.acceptsTooltip = null;
});
update();
}
public RecipeHandlerRef getRecipeHandlerRef() {
return RecipeHandlerRef.of(this.handler, getRecipeIndex());
}
protected int getRecipeIndex() {
return this.group.getRecipeIndex(this.activeMember);
}
public boolean containsRecipeIndex(int recipeIndex) {
return this.group.contains(recipeIndex);
}
public HandlerInfo getHandlerInfo() {
return this.handlerInfo;
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
public void showAsWidget(boolean show) {
this.showAsWidget = show;
}
@Override
public void update() {
if (this.showAsWidget || !this.handlerInfo.isAllowOverflowY()) {
int maxHeight = 0;
for (int member = 0; member < this.group.size(); member++) {
final int recipeHeight = this.handler.getRecipeHeight(this.group.getRecipeIndex(member));
if (recipeHeight > 0) {
maxHeight = Math.max(maxHeight, recipeHeight);
}
}
this.w = Math.max(166, this.handlerInfo.getWidth());
this.h = (maxHeight > 0 ? maxHeight : this.handlerInfo.getHeight()) + this.handlerInfo.getYShift();
}
this.update = true;
}
public List<GuiRecipeButton> getRecipeButtons() {
return this.buttonCache.computeIfAbsent(this.activeMember, this::createButtons);
}
protected List<GuiRecipeButton> getRecipeButtonsIfInit() {
return this.buttonCache.getOrDefault(this.activeMember, Collections.emptyList());
}
protected List<GuiRecipeButton> createButtons(int member) {
if (this.group.getOutputs(member).isEmpty()) {
return Collections.emptyList();
}
return Collections
.unmodifiableList(initButtons(RecipeHandlerRef.of(this.handler, this.group.getRecipeIndex(member))));
}
protected List<GuiRecipeButton> initButtons(RecipeHandlerRef ref) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe != null) {
final UpdateRecipeButtonsEvent.Pre preEvent = new UpdateRecipeButtonsEvent.Pre(
guiRecipe,
ref,
this.handlerInfo);
if (MinecraftForge.EVENT_BUS.post(preEvent)) {
return preEvent.buttonList;
} else {
final UpdateRecipeButtonsEvent.Post postEvent = new UpdateRecipeButtonsEvent.Post(
guiRecipe,
ref,
getDefaultButtons(ref));
MinecraftForge.EVENT_BUS.post(postEvent);
return postEvent.buttonList;
}
} else {
return getDefaultButtons(ref);
}
}
protected List<GuiRecipeButton> getDefaultButtons(RecipeHandlerRef ref) {
GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
final List<GuiRecipeButton> buttons = new ArrayList<>();
final boolean showFavorites = NEIClientConfig.favoritesEnabled() && handlerInfo.getShowFavoritesButton();
final boolean showOverlay = handlerInfo.getShowOverlayButton();
final int x = Math.min(166, this.w) - GuiRecipeButton.BUTTON_WIDTH;
int y = this.h - GuiRecipeButton.BUTTON_HEIGHT - 6;
if (guiContainer instanceof IGuiContainerOverlay overlay) {
guiContainer = overlay.getFirstScreen();
}
if (showOverlay) {
buttons.add(new GuiOverlayButton(guiContainer, ref, x, y));
y -= GuiRecipeButton.BUTTON_HEIGHT + 1;
}
if (showFavorites) {
buttons.add(new GuiFavoriteButton(ref, x, y));
}
return buttons;
}
@Override
public void draw(int mouseX, int mouseY) {
final int yShift = this.handlerInfo.getYShift();
if (this.update) {
this.update = false;
if (tickCycle()) {
if (this.showAsWidget || !contains(mouseX, mouseY)) {
nextMember();
}
updatePermutations(this.lastcycle);
}
for (GuiRecipeButton button : getRecipeButtons()) {
button.update();
}
}
final int recipeIndex = getRecipeIndex();
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
GL11.glTranslatef(this.x, this.y + yShift, 0);
this.handler.drawBackground(recipeIndex);
RenderHelper.enableGUIStandardItemLighting();
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
GL11.glColor4f(1, 1, 1, 1);
GuiContainerManager.enableMatrixStackLogging();
for (PositionedStack pStack : getInputs()) {
if (!this.renderPermutations.contains(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getCatalysts()) {
if (!this.renderPermutations.contains(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getOutputs()) {
drawItem(pStack, mouseX, mouseY, yShift, false);
}
GuiContainerManager.disableMatrixStackLogging();
this.handler.drawForeground(recipeIndex);
final GuiRecipeButton overlayButton = forEachButtons(
button -> (this.showAsWidget ? button instanceof GuiOverlayButton : button.contains(mouseX, mouseY))
? button
: null,
null);
if (overlayButton != null) {
NEIClientUtils.gl2DRenderContext(() -> overlayButton.drawItemOverlay());
}
GL11.glTranslatef(-this.x, -this.y - yShift, 0);
GL11.glPopAttrib();
if (!this.showAsWidget) {
final Minecraft mc = NEIClientUtils.mc();
for (GuiRecipeButton button : getRecipeButtons()) {
button.xPosition += this.x;
button.yPosition += this.y;
button.drawButton(mc, mouseX, mouseY);
button.xPosition -= this.x;
button.yPosition -= this.y;
}
}
DebugHandlerWidget.instance.drawGuiPlaceholder(this);
}
protected void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) {
pStack.draw(mouseX - this.x, mouseY - this.y - yShift);
if (this.handlerInfo.getShowBadge()) {
drawBadge(pStack, input);
}
if (pStack.contains(mouseX - this.x, mouseY - this.y - yShift)) {
NEIClientUtils.gl2DRenderContext(
() -> GuiDraw.drawRect(pStack.relx, pStack.rely, pStack.width, pStack.height, 0x80FFFFFF));
}
}
protected List<Badge> getBadges(PositionedStack pStack, boolean input) {
return this.badgeCache.computeIfAbsent(pStack, k -> {
final List<Badge> badges = k.getBadges();
if (badges == null) {
if (input) {
if (StackInfo.getAmount(pStack.item) == 0) {
return Collections.singletonList(Badge.notConsumed());
} else if (pStack.getChance() == 0) {
return Collections.singletonList(Badge.notConsumedParallel());
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.consumeChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.outputChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
return Collections.emptyList();
}
return badges;
});
}
protected void drawBadge(PositionedStack pStack, boolean input) {
final List<Badge> badges = getBadges(pStack, input);
for (Badge badge : badges) {
badge.draw(new Rectangle4i(pStack.relx, pStack.rely, pStack.width, pStack.height));
}
}
@Override
public boolean handleKeyPress(int keyID, char keyChar) {
if (NEIClientConfig.favoritesEnabled()
&& KeyManager.isHashDown("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH)) {
final Point mouse = GuiDraw.getMousePosition();
final PositionedStack hovered = getCyclingStackMouseOver(mouse.x, mouse.y);
if (hovered != null && getPermutations(hovered).size() > 1) {
FavoriteRecipes.toggleFavoriteItem(hovered.item);
return true;
}
}
return false;
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
final Point mouse = GuiDraw.getMousePosition();
forEachButtons(button -> {
if (button.contains(mouse.x, mouse.y)) {
button.lastKeyTyped(keyChar, keyID);
}
return null;
}, null);
}
@Override
public boolean handleClick(int mouseX, int mouseY, int button) {
if (ShortcutInputHandler.handleMouseClick(getStackMouseOver(mouseX, mouseY))) {
return true;
}
if (button == 0) {
final Minecraft mc = NEIClientUtils.mc();
if (forEachButtons(guibutton -> {
if (guibutton.mousePressed(mc, mouseX, mouseY)) {
NEIClientUtils.playClickSound();
guibutton.mouseReleased(mouseX, mouseY);
return true;
}
return null;
}, false)) {
return true;
}
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return false;
}
return this.handler.mouseClicked(guiRecipe, button, getRecipeIndex());
}
@Override
public List<String> handleTooltip(int mouseX, int mouseY, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
final List<String> buttonTooltip = forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleTooltip(tooltip) : null,
null);
if (buttonTooltip != null) {
return buttonTooltip;
}
return this.handler.handleTooltip(guiRecipe, tooltip, getRecipeIndex());
}
@Override
public List<String> handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
tooltip = this.handler.handleItemTooltip(guiRecipe, itemstack, tooltip, getRecipeIndex());
final PositionedStack cycling = itemstack != null ? getCyclingStackMouseOver(mousex, mousey) : null;
final PositionedStack hovered = cycling != null ? cycling
: itemstack != null ? getOutputStackMouseOver(mousex, mousey) : null;
if (hovered != null) {
if (this.handlerInfo.getShowBadge()) {
final List<Badge> badges = getBadges(hovered, cycling != null);
for (Badge badge : badges) {
if (badge.getTooltip() != null && !badge.getTooltip().isEmpty()) {
tooltip.addAll(badge.getTooltip());
}
}
}
final List<String> customTooltip = hovered.getTooltip();
if (customTooltip != null && !customTooltip.isEmpty()) {
tooltip.addAll(customTooltip);
}
}
final Point slotKey = cycling != null ? this.group.getSlotKey(cycling) : null;
if (slotKey == null || !NEIClientConfig.showCycledIngredientsTooltip()
|| this.group.getPermutations(slotKey).size() <= 1) {
this.acceptsTooltip = null;
} else if (this.acceptsTooltip == null || !slotKey.equals(this.acceptsTooltip.tooltipGUID)) {
this.acceptsTooltip = AcceptsFollowingTooltipLineHandler
.of(slotKey, this.group.getPermutations(slotKey), cycling.item);
if (this.acceptsTooltip != null && cycling.getAcceptsLabel() != null) {
this.acceptsTooltip.setLabel(cycling.getAcceptsLabel());
}
}
if (this.acceptsTooltip != null) {
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.acceptsTooltip));
}
final Point outputKey = cycling == null && hovered != null && this.group.size() > 1
? this.group.getSlotKey(hovered)
: null;
if (outputKey == null || !NEIClientConfig.showCycledIngredientsTooltip()) {
this.variantsTooltip = null;
this.variantsSlotKey = null;
} else if (!outputKey.equals(this.variantsSlotKey)) {
final List<ItemStack> variants = this.group.getOutputVariants(outputKey);
this.variantsSlotKey = outputKey;
this.variantsTooltip = variants.size() > 1
? new ItemsTooltipLineHandler(NEIClientUtils.translate("recipe.group"), variants, false, 4)
: null;
if (this.variantsTooltip != null) {
this.variantsTooltip.setActiveStack(hovered.item);
}
}
if (this.variantsTooltip != null) {
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.variantsTooltip));
}
return tooltip;
}
@Override
public Map<String, String> handleHotkeys(int mouseX, int mouseY, Map<String, String> hotkeys) {
final Map<String, String> buttonHotkeys = forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleHotkeys(mouseX, mouseY, hotkeys) : null,
hotkeys);
final PositionedStack hovered = getCyclingStackMouseOver(mouseX, mouseY);
if (hovered != null && getPermutations(hovered).size() > 1) {
hotkeys.put(
KeyManager.getKeyName("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH),
NEIClientUtils.translate("recipe.favorite.toggle"));
}
if (hovered == null && this.group.size() > 1 && getOutputStackMouseOver(mouseX, mouseY) != null) {
hotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH,
NEIMouseUtils.MOUSE_BTN_NONE + NEIMouseUtils.MOUSE_SCROLL),
NEIClientUtils.translate("recipe.group.scroll", this.activeMember + 1, this.group.size()));
}
if (hovered != null && this.acceptsTooltip != null) {
buttonHotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH,
NEIMouseUtils.MOUSE_BTN_NONE + NEIMouseUtils.MOUSE_SCROLL),
NEIClientUtils.translate("recipe.accepts.scroll"));
}
return buttonHotkeys;
}
@Override
public boolean onMouseWheel(int scroll, int mx, int my) {
if (forEachButtons(button -> button.contains(mx, my) ? button.mouseScrolled(scroll) : null, false)) {
return true;
}
if (NEIClientUtils.shiftKey()) {
final PositionedStack hovered = getCyclingStackMouseOver(mx, my);
if (hovered != null && scrollPermutations(scroll, hovered)) {
return true;
}
if (hovered == null && getOutputStackMouseOver(mx, my) != null && scrollMembers(scroll)) {
return true;
}
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
return guiRecipe != null && this.handler.mouseScrolled(guiRecipe, scroll, getRecipeIndex());
}
protected boolean scrollPermutations(int scroll, PositionedStack hovered) {
final Point slotKey = this.group.getSlotKey(hovered);
final List<ItemStack> items = this.group.getPermutations(slotKey);
if (items.size() <= 1) {
return false;
}
final List<PositionedStack> slots = new ArrayList<>(this.group.size());
for (int member = 0; member < this.group.size(); member++) {
slots.add(this.group.getSlot(member, slotKey));
}
final int anchor = getCursorMember();
final int currentItem = PermutationsCycler.indexOf(items, hovered.item);
final List<int[]> variants = new ArrayList<>();
int current = 0;
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
final ItemStack stack = items.get(itemIndex);
int member = containsStack(slots.get(this.activeMember), stack) ? this.activeMember : -1;
for (int offset = 0; member == -1 && offset < slots.size(); offset++) {
if (containsStack(slots.get((anchor + offset) % slots.size()), stack)) {
member = (anchor + offset) % slots.size();
}
}
if (member != -1) {
if (itemIndex == currentItem) {
current = variants.size();
}
variants.add(new int[] { itemIndex, member });
}
}
if (variants.isEmpty()) {
return false;
}
final int[] next = variants.get(((current - scroll) % variants.size() + variants.size()) % variants.size());
switchMember(next[1], items.get(next[0]));
return true;
}
protected boolean scrollMembers(int scroll) {
final int size = this.group.size();
if (size <= 1) {
return false;
}
switchMember(((this.activeMember - scroll) % size + size) % size, null);
return true;
}
protected void switchMember(int member, ItemStack stack) {
final Map<Point, ItemStack> rendered = new HashMap<>();
for (PositionedStack pStack : getCyclingStacks()) {
rendered.put(this.group.getSlotKey(pStack), pStack.item);
}
setActiveMember(member);
for (PositionedStack pStack : getCyclingStacks()) {
final ItemStack renderStack = containsStack(pStack, stack) ? stack
: rendered.get(this.group.getSlotKey(pStack));
if (containsStack(pStack, renderStack)) {
pStack.setPermutationToRender(renderStack);
}
}
updateTooltipActiveStack();
notifyPermutationsChanged();
}
protected boolean tickCycle() {
if (!NEIClientUtils.shiftKey() && (this.cycleticks++ / TICKS_PER_CYCLE) != this.lastcycle
|| this.lastcycle == -1) {
this.lastcycle = this.cycleticks / TICKS_PER_CYCLE;
return true;
}
return false;
}
protected void nextMember() {
final List<Integer> allowed = this.group.getAllowedMembers();
this.memberCursor = (this.memberCursor + 1) % allowed.size();
setActiveMember(allowed.get(this.memberCursor));
}
protected int getCursorMember() {
final List<Integer> allowed = this.group.getAllowedMembers();
return allowed.get(Math.max(0, this.memberCursor) % allowed.size());
}
protected void updatePermutations(int cycle) {
this.slotCycle = cycle;
for (PositionedStack pStack : getCyclingStacks()) {
updatePermutationsFor(pStack);
}
updateTooltipActiveStack();
notifyPermutationsChanged();
}
protected void updateTooltipActiveStack() {
if (this.acceptsTooltip != null && this.acceptsTooltip.tooltipGUID instanceof Point slotKey) {
final PositionedStack pStack = this.group.getSlot(this.activeMember, slotKey);
if (pStack != null) {
this.acceptsTooltip.setActiveStack(pStack.item);
}
}
if (this.variantsTooltip != null) {
final PositionedStack pStack = this.group.getOutputSlot(this.activeMember, this.variantsSlotKey);
if (pStack != null) {
this.variantsTooltip.setActiveStack(pStack.item);
}
}
}
protected void updatePermutationsFor(PositionedStack pStack) {
if (!this.renderPermutations.contains(pStack) || this.renderPermutations.get(pStack).size() > 1) {
final ItemStack stack = this.renderPermutations.getCycled(pStack, this.slotCycle);
if (stack != null) {
pStack.setPermutationToRender(stack);
}
}
}
protected void setActiveMember(int member) {
if (member != this.activeMember) {
this.activeMember = member;
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.update();
}
}
}
protected boolean containsStack(PositionedStack pStack, ItemStack stack) {
return pStack != null && stack != null
&& PermutationsCycler.indexOf(pStack.getFilteredPermutations(), stack) != -1;
}
protected List<ItemStack> getPermutations(PositionedStack pStack) {
return this.group.getPermutations(this.group.getSlotKey(pStack));
}
protected void notifyPermutationsChanged() {
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.onPermutationsChanged();
}
}
protected <R> R forEachButtons(Function<GuiRecipeButton, R> callback, R defaultValue) {
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.xPosition += this.x;
button.yPosition += this.y;
final R result = callback.apply(button);
button.xPosition -= this.x;
button.yPosition -= this.y;
if (result != null) {
return result;
}
}
return defaultValue;
}
public boolean isFocusedRecipe(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStackOver : getOutputs()) {
if (pStackOver.contains(mx - this.x, my - this.y - yShift)) {
return true;
}
}
return false;
}
public PositionedStack getPositionedStackMouseOver(int mx, int my) {
final PositionedStack cycling = getCyclingStackMouseOver(mx, my);
return cycling != null ? cycling : getOutputStackMouseOver(mx, my);
}
protected PositionedStack getOutputStackMouseOver(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStack : getOutputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
return null;
}
protected PositionedStack getCyclingStackMouseOver(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStack : getInputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
for (PositionedStack pStack : getCatalysts()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
return null;
}
@Override
public ItemStack getStackMouseOver(int mx, int my) {
final PositionedStack pStack = getPositionedStackMouseOver(mx, my);
return pStack != null ? pStack.item : null;
}
protected GuiRecipe<?> getGuiRecipe() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
return guiRecipe;
}
return null;
}
protected List<PositionedStack> getInputs() {
return this.group.getInputs(this.activeMember);
}
protected List<PositionedStack> getOutputs() {
return this.group.getOutputs(this.activeMember);
}
protected List<PositionedStack> getCatalysts() {
return this.group.getCatalysts(this.activeMember);
}
protected List<PositionedStack> getCyclingStacks() {
return this.group.getCyclingStacks(this.activeMember);
}
}
@@ -682,6 +682,8 @@ nei.recipe.concurrent.usage=Concurrent Usage - Total
nei.recipe.concurrent.crafting=Concurrent Crafting - Total
nei.recipe.accepts=Accepts following
nei.recipe.accepts.scroll=Change Item
nei.recipe.group=Variants
nei.recipe.group.scroll=Change Variant (%s/%s)
nei.recipe.overlay=Recipe Overlay
nei.recipe.overlay.missing=Missing following