diff --git a/src/main/java/codechicken/nei/FavoriteRecipes.java b/src/main/java/codechicken/nei/FavoriteRecipes.java index f0271d3b..66dbe105 100644 --- a/src/main/java/codechicken/nei/FavoriteRecipes.java +++ b/src/main/java/codechicken/nei/FavoriteRecipes.java @@ -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; diff --git a/src/main/java/codechicken/nei/PositionedStack.java b/src/main/java/codechicken/nei/PositionedStack.java index 5cbd657b..2f11f649 100644 --- a/src/main/java/codechicken/nei/PositionedStack.java +++ b/src/main/java/codechicken/nei/PositionedStack.java @@ -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 tooltip; + + protected Function> tooltip; protected List badges; public PositionedStack(Object object, int x, int y, boolean genPerms) { @@ -115,11 +117,15 @@ public class PositionedStack implements Cloneable { } public void setTooltip(List tooltip) { - this.tooltip = tooltip; + this.tooltip = tooltip == null ? null : item -> tooltip; + } + + public void setTooltipProvider(Function> provider) { + this.tooltip = provider; } public List 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) { diff --git a/src/main/java/codechicken/nei/recipe/FireworkRecipeHandler.java b/src/main/java/codechicken/nei/recipe/FireworkRecipeHandler.java index ff03bbd2..92c20806 100644 --- a/src/main/java/codechicken/nei/recipe/FireworkRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/FireworkRecipeHandler.java @@ -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 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 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 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 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> 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> 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 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 getChargeBase(ItemStack shape, boolean flicker, boolean trail) { + final List 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 getRocketBase(int flight) { + final List 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> getDyeSets(int[] colors) { + final List 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> dyeSets = new ArrayList<>(); + + for (int count = 1; count < 9; count++) { + dyeSets.add(Collections.nCopies(count, dyes.get(0))); + } + + return dyeSets; + } + + private void addRecipes(List base, List> extraSets, int type) { + final Object groupId = new Object(); + + for (List extras : extraSets) { + final List 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 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 handleTooltip(GuiRecipe gui, List 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)); diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipe.java b/src/main/java/codechicken/nei/recipe/GuiRecipe.java index 17e1c5f9..2740664b 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipe.java @@ -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 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 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 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 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 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 extends GuiContainer i List 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 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 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 extends GuiContainer i final List 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()); diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipeButton.java b/src/main/java/codechicken/nei/recipe/GuiRecipeButton.java index f24d9f92..31f4f0a3 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipeButton.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipeButton.java @@ -15,12 +15,12 @@ public abstract class GuiRecipeButton extends GuiNEIButton { public static class UpdateRecipeButtonsEvent extends GuiScreenEvent { public List buttonList; - public NEIRecipeWidget recipeWidget; + public RecipeHandlerRef handlerRef; - public UpdateRecipeButtonsEvent(GuiRecipe gui, NEIRecipeWidget recipeWidget, + public UpdateRecipeButtonsEvent(GuiRecipe gui, RecipeHandlerRef handlerRef, List 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 buttonList) { - super(gui, recipeWidget, buttonList); + public Post(GuiRecipe gui, RecipeHandlerRef handlerRef, List buttonList) { + super(gui, handlerRef, buttonList); } } } diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index 0afcacd3..5f87eeb4 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -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. */ diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java deleted file mode 100644 index 2856e66a..00000000 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ /dev/null @@ -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 favoriteIndexes = new WeakHashMap<>(); - protected final Map> permutations = new WeakHashMap<>(); - protected final Map> 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 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 getRecipeButtons() { - - if (this.recipeButtons == null) { - if (!getOutputs().isEmpty()) { - this.recipeButtons = initButtons(); - } else { - this.recipeButtons = Collections.emptyList(); - } - } - - return Collections.unmodifiableList(this.recipeButtons); - } - - protected List 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 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 getDefaultButtons() { - GuiContainer guiContainer = NEIClientUtils.getGuiContainer(); - final List 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 getBadges(PositionedStack pStack, boolean input) { - - return this.badgeCache.computeIfAbsent(pStack, k -> { - final List 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 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 handleTooltip(int mouseX, int mouseY, List tooltip) { - final GuiRecipe guiRecipe = getGuiRecipe(); - - if (guiRecipe == null) { - return tooltip; - } - - final List 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 handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List 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 badges = getBadges(hovered, getOutputs().indexOf(hovered) == -1); - - for (Badge badge : badges) { - if (badge.getTooltip() != null && !badge.getTooltip().isEmpty()) { - tooltip.addAll(badge.getTooltip()); - } - } - } - - final List 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 handleHotkeys(int mouseX, int mouseY, Map hotkeys) { - final int yShift = this.handlerInfo.getYShift(); - final Map 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 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 list, ItemStack stack) { - for (int i = 0; i < list.size(); i++) { - if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(list.get(i), stack)) { - return i; - } - } - return -1; - } - - protected R forEachButtons(Function 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 GuiRecipeguiRecipe) { - return guiRecipe; - } - - return null; - } - - protected GuiContainer getFirstGui() { - final GuiContainer guiContainer = NEIClientUtils.getGuiContainer(); - - if (guiContainer instanceof GuiRecipeguiRecipe) { - 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 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 getInputs() { - return this.handlerRef.handler.getIngredientStacks(this.handlerRef.recipeIndex); - } - - protected List 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 getCatalysts() { - if (this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex) == null) { - return Collections.emptyList(); - } - return this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex); - } - -} diff --git a/src/main/java/codechicken/nei/recipe/RecipeHandlerRef.java b/src/main/java/codechicken/nei/recipe/RecipeHandlerRef.java index 57520575..626dc675 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeHandlerRef.java +++ b/src/main/java/codechicken/nei/recipe/RecipeHandlerRef.java @@ -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); diff --git a/src/main/java/codechicken/nei/recipe/RecipePageManager.java b/src/main/java/codechicken/nei/recipe/RecipePageManager.java index 9001ad57..654a4d41 100644 --- a/src/main/java/codechicken/nei/recipe/RecipePageManager.java +++ b/src/main/java/codechicken/nei/recipe/RecipePageManager.java @@ -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 recipeIndices = new ArrayList<>(); private List> 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 searchRecipes = this.handler.getFilteredRecipes(); final int numRecipes = searchRecipes.size(); + final List> groupedRecipes = new ArrayList<>(); + final Map groupEndIndices = new HashMap<>(); List 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 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 getRecipeIndices() { - return this.recipeIndices; + + if (this.pageWidgets.isEmpty()) { + return Collections.emptyList(); + } + + final List recipeIndices = new ArrayList<>(); + + for (Widget widget : this.pageWidgets.get(this.currentPageIndex)) { + if (widget instanceof RecipeWidget recipeWidget) { + recipeIndices.add(recipeWidget.getRecipeHandlerRef().recipeIndex); + } + } + + return recipeIndices; } } diff --git a/src/main/java/codechicken/nei/recipe/RecipeTooltipLineHandler.java b/src/main/java/codechicken/nei/recipe/RecipeTooltipLineHandler.java index 147d7a53..b11f5c3e 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeTooltipLineHandler.java +++ b/src/main/java/codechicken/nei/recipe/RecipeTooltipLineHandler.java @@ -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(); } } diff --git a/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java b/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java index 92f73614..23e8b079 100644 --- a/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java @@ -52,15 +52,10 @@ class SearchRecipeHandler { 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; } } diff --git a/src/main/java/codechicken/nei/recipe/debug/DebugHandlerWidget.java b/src/main/java/codechicken/nei/recipe/debug/DebugHandlerWidget.java index 9931642a..70f64a57 100644 --- a/src/main/java/codechicken/nei/recipe/debug/DebugHandlerWidget.java +++ b/src/main/java/codechicken/nei/recipe/debug/DebugHandlerWidget.java @@ -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(() -> { diff --git a/src/main/java/codechicken/nei/recipe/widget/PermutationsCycler.java b/src/main/java/codechicken/nei/recipe/widget/PermutationsCycler.java new file mode 100644 index 00000000..cfc3eed1 --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/widget/PermutationsCycler.java @@ -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 { + + protected final Map> permutations; + protected final Map favoriteCounts; + protected final Function> loader; + protected Runnable onInvalidate = null; + + protected int favoriteRevision = -1; + + public PermutationsCycler(Map> storage, Map favoriteStorage, + Function> loader) { + this.permutations = storage; + this.favoriteCounts = favoriteStorage; + this.loader = loader; + } + + public PermutationsCycler 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 get(K key) { + validate(); + List 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 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 list, ItemStack stack) { + for (int i = 0; i < list.size(); i++) { + if (NEIServerUtils.areStacksSameType(list.get(i), stack)) { + return i; + } + } + return -1; + } + +} diff --git a/src/main/java/codechicken/nei/recipe/widget/RecipeGroup.java b/src/main/java/codechicken/nei/recipe/widget/RecipeGroup.java new file mode 100644 index 00000000..010a5298 --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/widget/RecipeGroup.java @@ -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 SINGLE_MEMBER = Collections.singletonList(0); + + private final IRecipeHandler handler; + private final List recipeIndices; + private final HandlerInfo handlerInfo; + private final PermutationsCycler slotPermutations; + + private List allowedMembers = null; + + public RecipeGroup(IRecipeHandler handler, List recipeIndices) { + this.handler = handler; + this.recipeIndices = new ArrayList<>(recipeIndices); + this.handlerInfo = GuiRecipeTab.getHandlerInfo(handler); + this.slotPermutations = new PermutationsCycler( + 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 getPermutations(Point slotKey) { + return this.slotPermutations.get(slotKey); + } + + public List getInputs(int member) { + return this.handler.getIngredientStacks(getRecipeIndex(member)); + } + + public List 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 getCatalysts(int member) { + final int recipeIndex = getRecipeIndex(member); + + if (this.handler.getResultStack(recipeIndex) == null) { + return Collections.emptyList(); + } + + return this.handler.getOtherStacks(recipeIndex); + } + + public List getCyclingStacks(int member) { + final List 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 getOutputVariants(Point slotKey) { + final List 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 getAllowedMembers() { + this.slotPermutations.validate(); + + if (size() == 1) { + return SINGLE_MEMBER; + } + + if (this.allowedMembers == null) { + final Set favoriteSlots = new HashSet<>(); + + for (int member = 0; member < size(); member++) { + for (PositionedStack pStack : getCyclingStacks(member)) { + final Point slotKey = getSlotKey(pStack); + final List items = getPermutations(slotKey); + + if (items.size() > 1 && items.stream().anyMatch(FavoriteRecipes::containsManual)) { + favoriteSlots.add(slotKey); + } + } + } + + this.allowedMembers = filterMembers(favoriteSlots); + } + + return this.allowedMembers; + } + + protected List filterMembers(Set favoriteSlots) { + final List 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 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 collectSlotPermutations(Point slotKey) { + final List 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; + } + +} diff --git a/src/main/java/codechicken/nei/recipe/widget/RecipeWidget.java b/src/main/java/codechicken/nei/recipe/widget/RecipeWidget.java new file mode 100644 index 00000000..00120e97 --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/widget/RecipeWidget.java @@ -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 renderPermutations; + + protected final Map> badgeCache = new WeakHashMap<>(); + protected final Map> 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 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( + 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 getRecipeButtons() { + return this.buttonCache.computeIfAbsent(this.activeMember, this::createButtons); + } + + protected List getRecipeButtonsIfInit() { + return this.buttonCache.getOrDefault(this.activeMember, Collections.emptyList()); + } + + protected List 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 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 getDefaultButtons(RecipeHandlerRef ref) { + GuiContainer guiContainer = NEIClientUtils.getGuiContainer(); + final List 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 getBadges(PositionedStack pStack, boolean input) { + + return this.badgeCache.computeIfAbsent(pStack, k -> { + final List 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 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 handleTooltip(int mouseX, int mouseY, List tooltip) { + final GuiRecipe guiRecipe = getGuiRecipe(); + + if (guiRecipe == null) { + return tooltip; + } + + final List 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 handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List 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 badges = getBadges(hovered, cycling != null); + + for (Badge badge : badges) { + if (badge.getTooltip() != null && !badge.getTooltip().isEmpty()) { + tooltip.addAll(badge.getTooltip()); + } + } + } + + final List 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 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 handleHotkeys(int mouseX, int mouseY, Map hotkeys) { + final Map 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 items = this.group.getPermutations(slotKey); + + if (items.size() <= 1) { + return false; + } + + final List 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 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 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 allowed = this.group.getAllowedMembers(); + + this.memberCursor = (this.memberCursor + 1) % allowed.size(); + setActiveMember(allowed.get(this.memberCursor)); + } + + protected int getCursorMember() { + final List 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 getPermutations(PositionedStack pStack) { + return this.group.getPermutations(this.group.getSlotKey(pStack)); + } + + protected void notifyPermutationsChanged() { + for (GuiRecipeButton button : getRecipeButtonsIfInit()) { + button.onPermutationsChanged(); + } + } + + protected R forEachButtons(Function 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 GuiRecipeguiRecipe) { + return guiRecipe; + } + + return null; + } + + protected List getInputs() { + return this.group.getInputs(this.activeMember); + } + + protected List getOutputs() { + return this.group.getOutputs(this.activeMember); + } + + protected List getCatalysts() { + return this.group.getCatalysts(this.activeMember); + } + + protected List getCyclingStacks() { + return this.group.getCyclingStacks(this.activeMember); + } + +} diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 87874ab3..5e69212e 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -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