Add getResultStacks/getAuxiliaryStacks to IRecipeHandler (#1105)

This commit is contained in:
slprime authored and GitHub committed 2026-11-03 17:33:07 +02:00
1 parent aea4c2866a
commit ae0d6c6108
17 files changed
+277 -216

No files matched your search

@@ -9,7 +9,6 @@ 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;
import java.util.List;
@@ -33,7 +32,6 @@ import codechicken.nei.api.API;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.StackInfo;
@@ -94,7 +92,7 @@ public class FavoriteRecipes {
for (int i = 0; i < handler.numRecipes(); i++) {
RecipeHandlerRef ref = null;
for (PositionedStack result : getOutputs(handler, i)) {
for (PositionedStack result : handler.getResultStacks(i)) {
ItemStack stack = StackInfo.getItemStackWithMinimumDamage(result.items);
final FastKey key = new FastKey(stack);
@@ -129,11 +127,6 @@ public class FavoriteRecipes {
updateSubsetVisibility();
}
protected List<PositionedStack> getOutputs(IRecipeHandler handler, int recipeIndex) {
final PositionedStack pStackResult = handler.getResultStack(recipeIndex);
return pStackResult != null ? Collections.singletonList(pStackResult) : handler.getOtherStacks(recipeIndex);
}
};
public static final String FAVORITE_ITEM = "FavoriteItem";
@@ -13,7 +13,6 @@ import java.util.stream.IntStream;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.FluidContainerRegistry;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
@@ -24,6 +23,7 @@ import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeInfo;
import codechicken.nei.recipe.StackInfo;
@@ -378,48 +378,19 @@ public class ItemCraftablesPanel
}
private static ItemStack getCheapResultItem(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
if (resultStack != null) {
return resultStack.item;
}
final List<PositionedStack> otherStacks = handler.getOtherStacks(recipeIndex);
for (PositionedStack otherStack : otherStacks) {
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|| StackInfo.getFluid(otherStack.items[0]) != null) {
return otherStack.item;
}
}
return otherStacks.isEmpty() ? null : otherStacks.get(0).item;
final PositionedStack result = Recipe
.getMainResult(handler.getResultStacks(recipeIndex), pStack -> pStack.items[0]);
return result != null ? result.item : null;
}
private static ItemStack getCheapResult(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
final List<PositionedStack> results = resultStack != null ? Collections.singletonList(resultStack)
: handler.getOtherStacks(recipeIndex);
final List<PositionedStack> results = handler.getResultStacks(recipeIndex);
if (results.isEmpty()) {
return null;
}
ItemStack stack = null;
if (results.size() > 1) {
for (PositionedStack result : results) {
if (!FluidContainerRegistry.isContainer(result.item) || StackInfo.getFluid(result.item) != null) {
stack = result.item;
break;
}
}
}
if (stack == null) {
stack = results.get(0).item;
}
final ItemStack stack = Recipe.getMainResult(results, pStack -> pStack.items[0]).item;
int stackSize = 0;
for (PositionedStack result : results) {
@@ -89,19 +89,19 @@ public class PresetsList {
return false;
}
final PositionedStack result = handler.getResultStack(recipeIndex);
final List<PositionedStack> outputs = handler.getResultStacks(recipeIndex);
if (result != null && matchPositionedStack(result)) {
if (matchPositionedStack(outputs, true)) {
return true;
}
final List<PositionedStack> others = handler.getOtherStacks(recipeIndex);
final List<PositionedStack> auxiliaries = handler.getAuxiliaryStacks(recipeIndex);
if (!others.isEmpty() && matchPositionedStack(others, true)) {
if (matchPositionedStack(auxiliaries, true)) {
return true;
}
return result == null && others.isEmpty();
return outputs.isEmpty() && auxiliaries.isEmpty();
}
private boolean matchPositionedStack(List<PositionedStack> items, boolean dir) {
@@ -24,7 +24,13 @@ public abstract class RecipeSearchField extends TextField implements ItemFilterP
field.setMaxStringLength(maxSearchLength);
field.setCursorPositionZero();
field.setSkipRecipeTokens(true);
((SearchTextFormatter) field.getFormatter()).setContextToken("<>");
field.setFormatter(new SearchTextFormatter(SearchField.searchParser) {
@Override
protected String getContextToken() {
return this.searchParser.getRecipeContextTokens();
}
});
this.field = field;
}
@@ -20,6 +20,10 @@ public class SearchTextFormatter implements TextFormatter {
this.contextToken = contextToken;
}
protected String getContextToken() {
return this.contextToken;
}
@Override
public String format(String text) {
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
@@ -32,13 +36,14 @@ public class SearchTextFormatter implements TextFormatter {
return SearchExpressionUtils.visitRecipeSearchExpression(text, visitor);
} else {
final StringBuilder formattedText = new StringBuilder();
final String contextToken = getContextToken();
for (String[] orQuery : searchParser.splitByDelimiters(text, "|", false)) {
final String filterText = orQuery[1];
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.OR + orQuery[0]);
for (String[] contextPart : searchParser.splitByDelimiters(filterText, this.contextToken, false)) {
for (String[] contextPart : searchParser.splitByDelimiters(filterText, contextToken, false)) {
if (!contextPart[0].isEmpty()) {
formattedText.append(
@@ -74,7 +79,11 @@ public class SearchTextFormatter implements TextFormatter {
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"");
}
if (!token.rawText.isEmpty()) {
if (patternMode == 1 && !token.quotes && isRegexToken(token.rawText)) {
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.REGEX + token.rawText.substring(0, 2))
.append(tokenColor + token.rawText.substring(2, token.rawText.length() - 1))
.append(SearchExpressionUtils.HIGHLIGHTS.REGEX + "/");
} else if (!token.rawText.isEmpty()) {
formattedText.append(tokenColor + token.rawText);
}
@@ -93,4 +102,8 @@ public class SearchTextFormatter implements TextFormatter {
return formattedText.toString();
}
}
private static boolean isRegexToken(String search) {
return search.length() >= 3 && search.regionMatches(true, 0, "r/", 0, 2) && search.endsWith("/");
}
}
@@ -194,6 +194,7 @@ public class SearchTokenParser {
if (patternMode != 3) {
return this.filtersCache.computeIfAbsent(skipRecipeTokens + ":" + filterText, _t -> {
final List<ItemFilter> searchTokens = new ArrayList<>();
final String contextTokens = skipRecipeTokens ? getRecipeContextTokens() : "";
for (String[] subQuery : splitByDelimiters(filterText, "|", false)) {
@@ -203,9 +204,10 @@ public class SearchTokenParser {
final List<ItemFilter> tokens = new ArrayList<>();
for (String[] contextQuery : splitByDelimiters(subQuery[1], skipRecipeTokens ? "<>" : "", false)) {
for (String[] contextQuery : splitByDelimiters(subQuery[1], contextTokens, false)) {
if (contextQuery[1].isEmpty() || contextQuery[0].startsWith(">")) {
if (contextQuery[1].isEmpty() || contextQuery[0].startsWith(">")
|| contextQuery[0].startsWith("^")) {
continue;
}
@@ -277,7 +279,7 @@ public class SearchTokenParser {
final List<IRecipeFilter> contextList = new ArrayList<>();
for (String[] contextQuery : splitByDelimiters(orQueryPart[1], "<>", false)) {
for (String[] contextQuery : splitByDelimiters(orQueryPart[1], getRecipeContextTokens(), false)) {
if (contextQuery[1].isEmpty()) {
continue;
@@ -436,7 +438,9 @@ public class SearchTokenParser {
final List<String[]> tokens = new ArrayList<>();
final int length = input.length();
final boolean regexTokens = NEIClientConfig.getIntSetting("inventory.search.patternMode") == 1;
boolean insideQuotes = false;
boolean insideRegex = false;
String token = "";
int lastEnd = 0;
int index = 0;
@@ -444,6 +448,15 @@ public class SearchTokenParser {
while (index < length) {
final char ch = input.charAt(index);
if (insideRegex) {
if (ch == '/' && input.charAt(index - 1) != '\\') {
insideRegex = false;
}
index++;
continue;
}
if (!insideQuotes && delimiters.indexOf(ch) >= 0
&& (!space || index == 0 || input.charAt(index - 1) == ' ' || lastEnd == index)) {
@@ -464,6 +477,9 @@ public class SearchTokenParser {
if (ch == '"' && (index == 0 || input.charAt(index - 1) != '\\')) {
insideQuotes = !insideQuotes;
} else if (regexTokens && !insideQuotes && isRegexStart(input, index)) {
insideRegex = true;
index++;
}
index++;
@@ -474,6 +490,12 @@ public class SearchTokenParser {
return tokens;
}
private static boolean isRegexStart(String input, int index) {
return Character.toLowerCase(input.charAt(index)) == 'r' && index + 1 < input.length()
&& input.charAt(index + 1) == '/'
&& (index == 0 || !Character.isLetterOrDigit(input.charAt(index - 1)));
}
public String getPrefixes() {
if (this.prefixes == null) {
final StringBuilder builder = new StringBuilder();
@@ -490,6 +512,11 @@ public class SearchTokenParser {
return this.prefixes;
}
public String getRecipeContextTokens() {
// '^' is the start anchor and the negated class in regex mode
return NEIClientConfig.getIntSetting("inventory.search.patternMode") == 2 ? "<>" : "<>^";
}
public char getRedefinedPrefix(char prefix) {
return this.prefixRedefinitions.getOrDefault(prefix, prefix);
}
@@ -1,44 +0,0 @@
package codechicken.nei.filter;
import net.minecraft.item.ItemStack;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.recipe.IRecipeHandler;
public class AllOthersRecipeFilter implements IRecipeFilter {
private final ItemFilter filter;
public AllOthersRecipeFilter(ItemFilter filter) {
this.filter = filter;
}
@Override
public boolean matches(IRecipeHandler handler, int recipeIndex) {
for (PositionedStack pStack : handler.getOtherStacks(recipeIndex)) {
if (!match(pStack)) {
return false;
}
}
return true;
}
protected boolean match(PositionedStack pStack) {
if (pStack == null) {
return false;
}
for (ItemStack stack : pStack.items) {
if (filter.matches(stack)) {
return true;
}
}
return false;
}
}
@@ -1,44 +0,0 @@
package codechicken.nei.filter;
import net.minecraft.item.ItemStack;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.recipe.IRecipeHandler;
public class AnyOthersRecipeFilter implements IRecipeFilter {
private final ItemFilter filter;
public AnyOthersRecipeFilter(ItemFilter filter) {
this.filter = filter;
}
@Override
public boolean matches(IRecipeHandler handler, int recipeIndex) {
for (PositionedStack pStack : handler.getOtherStacks(recipeIndex)) {
if (match(pStack)) {
return true;
}
}
return false;
}
protected boolean match(PositionedStack pStack) {
if (pStack == null) {
return false;
}
for (ItemStack stack : pStack.items) {
if (filter.matches(stack)) {
return true;
}
}
return false;
}
}
@@ -16,7 +16,8 @@ public class RecipeFilter implements IRecipeFilter {
ANY,
INPUT,
OUTPUT;
OUTPUT,
AUXILIARY;
public static FilterContext fromChar(char c) {
switch (c) {
@@ -24,6 +25,8 @@ public class RecipeFilter implements IRecipeFilter {
return INPUT;
case '>':
return OUTPUT;
case '^':
return AUXILIARY;
default:
return ANY;
}
@@ -48,17 +51,14 @@ public class RecipeFilter implements IRecipeFilter {
return this.anyMatch;
}
if (this.context == FilterContext.ANY || this.context == FilterContext.OUTPUT) {
final PositionedStack result = handler.getResultStack(recipeIndex);
if (result != null && matchPositionedStack(result) == this.anyMatch) {
return this.anyMatch;
}
if (matchPositionedStack(handler.getOtherStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
if ((this.context == FilterContext.ANY || this.context == FilterContext.OUTPUT)
&& matchPositionedStack(handler.getResultStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
if ((this.context == FilterContext.ANY || this.context == FilterContext.AUXILIARY)
&& matchPositionedStack(handler.getAuxiliaryStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
return !this.anyMatch;
@@ -200,6 +200,22 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
"recipe.search.tooltip.output",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + ">"));
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
if (patternMode != 2) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.auxiliary",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + "^"));
}
if (patternMode == 3) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.all",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + "<&"));
}
for (ISearchParserProvider provider : SearchField.searchParser.getProviders()) {
if (provider.getSearchMode() == SearchMode.PREFIX) {
addPrefixTooltip(tooltip, provider);
@@ -222,6 +238,14 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
"recipe.search.tooltip.exact",
SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"",
SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\""));
if (patternMode == 1 || patternMode == 3) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.regex",
SearchExpressionUtils.HIGHLIGHTS.REGEX + "r/",
SearchExpressionUtils.HIGHLIGHTS.REGEX + "/"));
}
} else {
tooltip.add(NEIClientUtils.translate("recipe.search.tooltip.shift"));
}
@@ -1,5 +1,6 @@
package codechicken.nei.recipe;
import java.util.Collections;
import java.util.List;
import net.minecraft.client.gui.inventory.GuiContainer;
@@ -77,19 +78,48 @@ public interface IRecipeHandler {
List<PositionedStack> getIngredientStacks(int recipe);
/**
* Callers should read {@link #getResultStacks(int)} and {@link #getAuxiliaryStacks(int)} instead.
*
* @param recipe The recipe index to get items for.
* @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your
* recipe drawing space. For example fuel in furnaces.
* @return The fuel or catalysts when {@link #getResultStack(int)} is present, otherwise all the results of the
* recipe. Relative to the top left corner of your recipe drawing space.
*/
List<PositionedStack> getOtherStacks(int recipe);
default List<PositionedStack> getOtherStacks(int recipe) {
return Collections.emptyList();
}
/**
* Callers should read {@link #getResultStacks(int)} instead.
*
* @param recipe The recipe index to get the result for.
* @return The recipe result {@link PositionedStack} relative to the top left corner of your recipe drawing space.
* @return The single result, or {@code null} when the recipe has several results and returns them from
* {@link #getOtherStacks(int)}. Relative to the top left corner of your recipe drawing space.
*/
PositionedStack getResultStack(int recipe);
default PositionedStack getResultStack(int recipe) {
return null;
}
/**
* Built from {@link #getResultStack(int)} and {@link #getOtherStacks(int)}. Don't override it, return the results
* from those methods so mods that still read them see the same results.
*
* @return The results: the result stack when present, otherwise the other stacks.
*/
default List<PositionedStack> getResultStacks(int recipe) {
final PositionedStack result = getResultStack(recipe);
return result != null ? Collections.singletonList(result) : getOtherStacks(recipe);
}
/**
* Override it only when the recipe has several results and also fuel or catalysts: the results then go to
* {@link #getOtherStacks(int)} and the catalysts can't be returned from the old methods.
*
* @return The stacks that are neither ingredients nor results (fuel, catalysts): the other stacks when a result
* stack is present.
*/
default List<PositionedStack> getAuxiliaryStacks(int recipe) {
return getResultStack(recipe) != null ? getOtherStacks(recipe) : Collections.emptyList();
}
/**
* @param recipe The recipe index to get the group id for.
@@ -7,6 +7,7 @@ import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.item.Item;
@@ -65,19 +66,11 @@ public class Recipe {
public static RecipeId of(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredients) {
final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName();
PositionedStack pStackResult = handler.getResultStack(recipeIndex);
final PositionedStack result = getMainResult(
handler.getResultStacks(recipeIndex),
pStack -> pStack.items[0]);
if (pStackResult == null) {
for (PositionedStack otherStack : handler.getOtherStacks(recipeIndex)) {
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|| StackInfo.getFluid(otherStack.items[0]) != null) {
pStackResult = otherStack;
break;
}
}
}
return new RecipeId(extractItem(pStackResult), handlerName, extractIngredients(ingredients));
return new RecipeId(extractItem(result), handlerName, extractIngredients(ingredients));
}
public static RecipeId of(JsonObject json) {
@@ -404,12 +397,8 @@ public class Recipe {
ingredients.add(RecipeIngredient.of(positionedStack));
}
if (handler.getResultStack(recipeIndex) != null) {
results.add(RecipeIngredient.of(handler.getResultStack(recipeIndex)));
} else {
for (PositionedStack positionedStack : handler.getOtherStacks(recipeIndex)) {
results.add(RecipeIngredient.of(positionedStack));
}
for (PositionedStack positionedStack : handler.getResultStacks(recipeIndex)) {
results.add(RecipeIngredient.of(positionedStack));
}
return new Recipe(handlerName, ingredients, results);
@@ -494,18 +483,8 @@ public class Recipe {
return null;
}
if (stack == null && this.results.length > 1) {
for (RecipeIngredient ingr : this.results) {
if (!FluidContainerRegistry.isContainer(ingr.getItemStack())
|| StackInfo.getFluid(ingr.getItemStack()) != null) {
stack = ingr.getItemStack();
break;
}
}
}
if (stack == null) {
stack = this.results[0].getItemStack();
stack = getMainResult(getResults(), RecipeIngredient::getItemStack).getItemStack();
}
int stackSize = 0;
@@ -519,6 +498,19 @@ public class Recipe {
return StackInfo.withAmount(stack, stackSize);
}
public static <T> T getMainResult(List<T> results, Function<T, ItemStack> stackOf) {
for (T result : results) {
final ItemStack stack = stackOf.apply(result);
if (!FluidContainerRegistry.isContainer(stack) || StackInfo.getFluid(stack) != null) {
return result;
}
}
return results.isEmpty() ? null : results.get(0);
}
public RecipeId getRecipeId() {
if (this.recipeId == null) {
@@ -4,6 +4,7 @@ import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -176,6 +177,16 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return null;
}
/**
* Read only when {@link #getResult()} is {@code null} and {@link #getOtherStacks()} returns the results, see
* {@link IRecipeHandler#getAuxiliaryStacks(int)}.
*
* @return The fuel or catalysts of a recipe with several results.
*/
public List<PositionedStack> getAuxiliaries() {
return Collections.emptyList();
}
@Deprecated
public List<PositionedStack> getCycledIngredients(int cycle, List<PositionedStack> ingredients) {
return ingredients;
@@ -641,6 +652,16 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return arecipes.get(recipe).getOtherStacks();
}
@Override
public List<PositionedStack> getAuxiliaryStacks(int recipe) {
// goes through the handler methods, subclasses may override them instead of the cached recipe ones
if (getResultStack(recipe) != null) {
return getOtherStacks(recipe);
}
return arecipes.get(recipe).getAuxiliaries();
}
public void onUpdate() {
cycleticks++;
}
@@ -225,15 +225,10 @@ public class RecipeGroup {
protected MemberStacks loadStacks(int member) {
final int recipeIndex = getRecipeIndex(member);
final List<PositionedStack> inputs = new ArrayList<>(this.handler.getIngredientStacks(recipeIndex));
final PositionedStack pStackResult = this.handler.getResultStack(recipeIndex);
final List<PositionedStack> others = new ArrayList<>(this.handler.getOtherStacks(recipeIndex));
if (pStackResult != null) {
return new MemberStacks(inputs, Collections.singletonList(pStackResult), others);
}
return new MemberStacks(inputs, others, Collections.emptyList());
return new MemberStacks(
new ArrayList<>(this.handler.getIngredientStacks(recipeIndex)),
new ArrayList<>(this.handler.getResultStacks(recipeIndex)),
new ArrayList<>(this.handler.getAuxiliaryStacks(recipeIndex)));
}
// re-reads the member stacks from the handler, keeps the cached instances when nothing changed
@@ -2,15 +2,12 @@ package codechicken.nei.search;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import codechicken.nei.ItemList.EverythingItemFilter;
import codechicken.nei.SearchTokenParser;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.filter.AllMultiRecipeFilter;
import codechicken.nei.filter.AllOthersRecipeFilter;
import codechicken.nei.filter.AnyOthersRecipeFilter;
import codechicken.nei.filter.RecipeFilter;
import codechicken.nei.filter.RecipeFilter.FilterContext;
@@ -51,11 +48,7 @@ public class RecipeFilterVisitor extends AbstractSearchExpressionVisitor<IRecipe
case 1:
return new RecipeFilter(FilterContext.OUTPUT, !ctx.allRecipe, itemFilter);
case 2:
return getAllOrAnyFilter(
ctx.allRecipe,
itemFilter,
AnyOthersRecipeFilter::new,
AllOthersRecipeFilter::new);
return new RecipeFilter(FilterContext.AUXILIARY, !ctx.allRecipe, itemFilter);
// Doesn't support all by default
case 3:
return new RecipeFilter(FilterContext.ANY, !ctx.allRecipe, itemFilter);
@@ -64,15 +57,6 @@ public class RecipeFilterVisitor extends AbstractSearchExpressionVisitor<IRecipe
}
}
private IRecipeFilter getAllOrAnyFilter(boolean allRecipe, ItemFilter itemFilter,
Function<ItemFilter, IRecipeFilter> createAnyFilter, Function<ItemFilter, IRecipeFilter> createAllFilter) {
if (allRecipe) {
return createAllFilter.apply(itemFilter);
} else {
return createAnyFilter.apply(itemFilter);
}
}
private IRecipeFilter constructFilter(List<IRecipeFilter> filters) {
if (!filters.isEmpty()) {
// Propagate the result up
@@ -652,6 +652,8 @@ nei.recipe.search.tooltip.name=Filter Syntax
nei.recipe.search.tooltip.shift=§7Hold §6Shift§7 for more info
nei.recipe.search.tooltip.input=%s§ftext§8 - §7search in inputs
nei.recipe.search.tooltip.output=%s§ftext§8 - §7search in outputs
nei.recipe.search.tooltip.auxiliary=%s§ftext§8 - §7search in other stacks (fuel, catalysts)
nei.recipe.search.tooltip.all=%s§ftext§8 - §7all inputs must match (same with > and ^)
nei.recipe.search.tooltip.prefix=%stext§8 - §7%s
nei.recipe.search.tooltip.prefix.modName=search by mod
@@ -665,6 +667,7 @@ nei.recipe.search.tooltip.exclude=%s§ftext§8 - §7exclude items with text
nei.recipe.search.tooltip.exclude_recipe=%s§ftext§8 - §7exclude recipes with text
nei.recipe.search.tooltip.or=§fa%s§fb§8 - §7match a or b
nei.recipe.search.tooltip.exact=%s§ftext%s§8 - §7match exact phrase
nei.recipe.search.tooltip.regex=%s§ftext%s§8 - §7match regular expression
nei.recipe.shaped=Shaped Crafting
nei.recipe.shapeless=Shapeless Crafting
@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Named.named;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -32,6 +33,8 @@ import codechicken.nei.ItemList.AnyMultiItemFilter;
import codechicken.nei.ItemList.PatternItemFilter;
import codechicken.nei.SearchField.SearchParserProvider;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.search.IdentifierFilter;
@DisplayName("Search expression")
@@ -65,6 +68,7 @@ public class SearchTokenParserTest {
private static final ModeValue[] extendedPlusPatternMode = { patternModes[3] };
private static final ModeValue[] extendedPatternModes = { patternModes[1], patternModes[3] };
private static final ModeValue[] nonGrammarPatternModes = { patternModes[0], patternModes[1], patternModes[2] };
private static final ModeValue[] nonRegexPatternModes = { patternModes[0], patternModes[1], patternModes[3] };
private static final SearchParserProvider defaultParserProvider = parserProvider(
'\0',
@@ -140,6 +144,14 @@ public class SearchTokenParserTest {
spaceModes,
extendedPatternModes);
private static final List<Arguments> extendedOnlyAllSpaceModesTestProvider = combinationsProvider(
spaceModes,
new ModeValue[] { patternModes[1] });
private static final List<Arguments> nonRegexAllSpaceModesTestProvider = combinationsProvider(
spaceModes,
nonRegexPatternModes);
private static final List<Arguments> spaceIsAndExtendedPlusModeTestProvider = combinationsProvider(
spaceIsAndMode,
extendedPlusPatternMode);
@@ -176,6 +188,7 @@ public class SearchTokenParserTest {
config.when(() -> NEIClientConfig.getBooleanSetting(eq("inventory.search.logSearchExceptions")))
.thenReturn(false);
mockStatic(ItemInfo.class);
}
// All modes tests
@@ -321,6 +334,62 @@ public class SearchTokenParserTest {
assertTrue(matchedItems.contains(itemABC));
}
@DisplayName("Regex with delimiters")
@ParameterizedTest(name = "{argumentsWithNames}")
@FieldSource("extendedOnlyAllSpaceModesTestProvider")
public void testRegexWithDelimiters(int spaceMode, int patternMode) {
SearchTokenParser searchParser = new SearchTokenParser();
searchParser.addProvider(defaultParserProvider);
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
spaceMode,
patternMode,
searchParser,
"r/^" + itemA.getDisplayName() + "|^" + itemC.getDisplayName() + "/",
true);
assertEquals(4, matchedItems.size());
assertTrue(matchedItems.contains(itemA));
assertTrue(matchedItems.contains(itemC));
assertTrue(matchedItems.contains(itemAB));
assertTrue(matchedItems.contains(itemABC));
}
@DisplayName("Regex followed by delimiter")
@ParameterizedTest(name = "{argumentsWithNames}")
@FieldSource("extendedAllSpaceModesTestProvider")
public void testRegexFollowedByDelimiter(int spaceMode, int patternMode) {
SearchTokenParser searchParser = new SearchTokenParser();
searchParser.addProvider(defaultParserProvider);
Set<ItemStack> matchedItems = testFilterAgainstAllItems(
spaceMode,
patternMode,
searchParser,
"r/^" + itemA.getDisplayName() + "$/|" + itemC.getDisplayName());
assertEquals(3, matchedItems.size());
assertTrue(matchedItems.contains(itemA));
assertTrue(matchedItems.contains(itemC));
assertTrue(matchedItems.contains(itemABC));
}
@DisplayName("Recipe context")
@ParameterizedTest(name = "{argumentsWithNames}")
@FieldSource("nonRegexAllSpaceModesTestProvider")
public void testRecipeContext(int spaceMode, int patternMode) {
SearchTokenParser searchParser = new SearchTokenParser();
searchParser.addProvider(defaultParserProvider);
IRecipeHandler handler = mock(IRecipeHandler.class);
when(handler.getIngredientStacks(0)).thenReturn(positionedStacks(itemA));
when(handler.getResultStacks(0)).thenReturn(positionedStacks(itemB));
when(handler.getAuxiliaryStacks(0)).thenReturn(positionedStacks(itemC));
assertTrue(testRecipeFilter(spaceMode, patternMode, searchParser, "<" + itemA.getDisplayName(), handler));
assertTrue(testRecipeFilter(spaceMode, patternMode, searchParser, ">" + itemB.getDisplayName(), handler));
assertTrue(testRecipeFilter(spaceMode, patternMode, searchParser, "^" + itemC.getDisplayName(), handler));
assertFalse(testRecipeFilter(spaceMode, patternMode, searchParser, "^" + itemB.getDisplayName(), handler));
assertFalse(testRecipeFilter(spaceMode, patternMode, searchParser, ">" + itemC.getDisplayName(), handler));
}
// Extended+ specific tests
@DisplayName("Sequence")
@@ -403,10 +472,14 @@ public class SearchTokenParserTest {
private static Set<ItemStack> testFilterAgainstAllItems(int spaceMode, int patternMode,
SearchTokenParser searchParser, String search) {
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.patternMode"))).thenReturn(patternMode);
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.spaceMode"))).thenReturn(spaceMode);
return testFilterAgainstAllItems(spaceMode, patternMode, searchParser, search, false);
}
ItemFilter filter = searchParser.getFilter(search);
private static Set<ItemStack> testFilterAgainstAllItems(int spaceMode, int patternMode,
SearchTokenParser searchParser, String search, boolean skipRecipeTokens) {
setModes(spaceMode, patternMode);
ItemFilter filter = searchParser.getFilter(search, skipRecipeTokens);
if (filter instanceof SearchTokenParser.IsRegisteredItemFilter) {
filter = ((SearchTokenParser.IsRegisteredItemFilter) filter).filter;
}
@@ -415,6 +488,23 @@ public class SearchTokenParserTest {
return matchedItems;
}
private static boolean testRecipeFilter(int spaceMode, int patternMode, SearchTokenParser searchParser,
String search, IRecipeHandler handler) {
setModes(spaceMode, patternMode);
return searchParser.getRecipeFilter(search).matches(handler, 0);
}
private static void setModes(int spaceMode, int patternMode) {
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.patternMode"))).thenReturn(patternMode);
config.when(() -> NEIClientConfig.getIntSetting(eq("inventory.search.spaceMode"))).thenReturn(spaceMode);
}
private static List<PositionedStack> positionedStacks(ItemStack stack) {
PositionedStack pStack = mock(PositionedStack.class);
pStack.items = new ItemStack[] { stack };
return Collections.singletonList(pStack);
}
private static SearchParserProvider parserProvider(char prefix, String name,
SearchTokenParser.SearchMode searchMode, Function<Pattern, ItemFilter> createFilter) {
return new SearchParserProvider(prefix, name, EnumChatFormatting.RESET, createFilter) {