Skip to content

Commit

Permalink
Add helper for ingredient replacement
Browse files Browse the repository at this point in the history
Signed-off-by: TheSilkMiner <thesilkminer@outlook.com>
  • Loading branch information
TheSilkMiner committed May 23, 2021
1 parent d0f1109 commit 94501b6
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 41 deletions.
Expand Up @@ -42,7 +42,7 @@
* <td><strong>Ingredient Replacement</strong></td>
* <td>
* Following script method calls, a recipe may need to be replaced with an equivalent one, albeit with
* some ingredients replaced with others according to certain {@link ReplacementRule}s.
* some ingredients replaced with others according to certain {@link IReplacementRule}s.
* </td>
* <td>{@link IRecipeHandler#replaceIngredients(IRecipeManager, IRecipe, List)}</td>
* </tr>
Expand Down Expand Up @@ -105,6 +105,52 @@ public interface IRecipeHandler<T extends IRecipe<?>> {
Class<? extends IRecipe<?>> value();
}

/**
* Exception that indicates that the current recipe handler does not support replacing for the targeted recipe
* class.
*
* <p>Refer to {@link IRecipeHandler#replaceIngredients(IRecipeManager, IRecipe, List)} for more information
* regarding the exact semantics of this exception.</p>
*/
class ReplacementNotSupportedException extends Exception {
/**
* Constructs a new exception with the specified detail message.
*
* <p>The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.</p>
*
* @param message The detail message, which is saved for later retrieval by the {@link #getMessage()} method.
*/
public ReplacementNotSupportedException(final String message) {
super(message);
}

/**
* Constructs a new exception with the specified detail message and cause.
*
* <p>Note that the detail message associated with {@code cause} is <em>not</em> automatically incorporated in
* this exception's detail message.</p>
*
* @param message The detail message, which is saved for later retrieval by the {@link #getMessage()} method.
* @param cause The cause, which is saved for later retrieval by the {@link #getCause()} method. {@code null}
* is allowed and indicates that the cause is not available or not known.
*/
public ReplacementNotSupportedException(final String message, final Throwable cause) {
super(message, cause);
}
}

static <U> Optional<U> attemptReplacing(final U ingredient, final List<IReplacementRule<?>> rules) {
// TODO("Needs testing")
return rules.stream()
.filter(it -> it.getTargetedType().isAssignableFrom(ingredient.getClass()))
.map(IReplacementRule::<U>cast)
.reduce(
Optional.empty(),
(optional, rule) -> rule.getReplacement(optional.orElse(ingredient)),
(oldOptional, newOptional) -> newOptional.isPresent()? newOptional : oldOptional
);
}

/**
* Creates a String representation of a valid {@code addRecipe} (or alternative) call for the given subclass of
* {@link IRecipe}.
Expand All @@ -120,7 +166,7 @@ public interface IRecipeHandler<T extends IRecipe<?>> {
String dumpToCommandString(final IRecipeManager manager, final T recipe);

/**
* Handles the replacement of ingredients according to the given set of {@link ReplacementRule}s for the given
* Handles the replacement of ingredients according to the given set of {@link IReplacementRule}s for the given
* subclass of {@link IRecipe}.
*
* <p>This method should try to apply all of the applicable rules to the recipe. If one of the rules fails to apply,
Expand All @@ -129,33 +175,33 @@ public interface IRecipeHandler<T extends IRecipe<?>> {
* the replacement rules may or may not apply depending on the specific implementation: no specific contracts are
* enforced by this method.</p>
*
* <p>If a particular recipe handler does not support replacement, an {@link UnsupportedOperationException} should
* <p>If a particular recipe handler does not support replacement, a {@link ReplacementNotSupportedException} should
* be raised, along with a helpful error message. A recipe handler <strong>must</strong> be consistent, meaning that
* given the same recipe class, the behavior should be consistent: either an exception gets thrown or the
* replacement gets carried out.</p>
*
* @implSpec The {@code rules} list not only indicates the {@link ReplacementRule}s that should be applied, but also
* @implSpec The {@code rules} list not only indicates the {@link IReplacementRule}s that should be applied, but also
* the order in which these should be applied. In other words, the rule at position {@code 0} should be applied to
* the {@code recipe} before the rule in position {@code 1}. <strong>However</strong>, implementations are free to
* ignore this detail and reorder the rule application to optimize certain applications if needed. This reordering
* <strong>must</strong> guarantee that the resulting recipe behaves exactly as if the replacements were carried out
* in order.
*
* @implNote By default, this method throws an {@link UnsupportedOperationException}.
* @implNote By default, this method throws a {@link ReplacementNotSupportedException}.
*
* @param manager The recipe manager responsible for this kind of recipes.
* @param recipe The recipe whose ingredients should be replaced.
* @param rules A series of {@link ReplacementRule}s in the order they should be applied. Implementations are
* @param rules A series of {@link IReplacementRule}s in the order they should be applied. Implementations are
* nevertheless allowed to reorder these rules as they see fit. Refer to the implementation
* specifications for more details.
* @return An {@link Optional} holding the replaced recipe, if any replacements have been carried out. If no
* replacement rule affected the current recipe, the return value should be {@link Optional#empty()}. It is
* customary, though not required, that the value wrapped by the optional is a completely different object from
* {@code recipe} (i.e. {@code recipe != result.get()}).
* @throws UnsupportedOperationException If the current handler does not support replacing for the given recipe
* @throws ReplacementNotSupportedException If the current handler does not support replacing for the given recipe
* class.
*/
default Optional<T> replaceIngredients(final IRecipeManager manager, final T recipe, final List<ReplacementRule> rules) {
throw new UnsupportedOperationException("Replacement is not supported for this recipe type");
default Optional<T> replaceIngredients(final IRecipeManager manager, final T recipe, final List<IReplacementRule<?>> rules) throws ReplacementNotSupportedException {
throw new ReplacementNotSupportedException("Replacement is not supported for this recipe class");
}
}
Expand Up @@ -6,6 +6,7 @@
import com.blamejared.crafttweaker.api.util.InstantiationUtil;
import com.blamejared.crafttweaker.impl.item.MCItemStackMutable;
import net.minecraft.item.crafting.IRecipe;
import org.openzen.zenscript.codemodel.Modifiers;

import java.util.ArrayDeque;
import java.util.Arrays;
Expand Down Expand Up @@ -42,6 +43,9 @@ public void addClass(final Class<?> clazz) {
if (clazz.isInterface()) {
throw new IllegalArgumentException("Class " + clazz.getName() + " is an interface and cannot be annotated with @IRecipeHandler.For");
}
if (Modifiers.isAbstract(clazz.getModifiers())) {
throw new IllegalArgumentException("Class " + clazz.getName() + " is an abstract class and cannot be annotated with @IRecipeHandler.For");
}
//noinspection RedundantCast
Arrays.stream(clazz.getAnnotationsByType(IRecipeHandler.For.class))
.map(IRecipeHandler.For::value)
Expand Down
Expand Up @@ -3,7 +3,7 @@
import com.blamejared.crafttweaker.api.item.IIngredient;
import com.blamejared.crafttweaker.api.managers.IRecipeManager;
import com.blamejared.crafttweaker.api.recipes.IRecipeHandler;
import com.blamejared.crafttweaker.api.recipes.ReplacementRule;
import com.blamejared.crafttweaker.api.recipes.IReplacementRule;
import com.blamejared.crafttweaker.api.util.StringUtils;
import com.blamejared.crafttweaker.impl.item.MCItemStackMutable;
import com.google.common.collect.ImmutableMap;
Expand Down Expand Up @@ -55,23 +55,12 @@ public String dumpToCommandString(final IRecipeManager manager, final AbstractCo
}

@Override
public Optional<AbstractCookingRecipe> replaceIngredients(final IRecipeManager manager, final AbstractCookingRecipe recipe, final List<ReplacementRule> rules) {
public Optional<AbstractCookingRecipe> replaceIngredients(final IRecipeManager manager, final AbstractCookingRecipe recipe, final List<IReplacementRule<?>> rules) {

final Ingredient originalInput = recipe.getIngredients().get(0);
/*mutable*/ Ingredient input = originalInput;
for (final ReplacementRule rule : rules) {
if (rule.shouldReplace(input)) {
input = rule.getVanillaTo();
}
}

if (originalInput == input) return Optional.empty();

final AbstractCookingRecipe newRecipe = LOOKUP.get(recipe.getType())
.getSecond()
.create(recipe.getId(), recipe.getGroup(), input, recipe.getRecipeOutput(), recipe.getExperience(), recipe.getCookTime());

return Optional.of(newRecipe);
return IRecipeHandler.attemptReplacing(recipe.getIngredients().get(0), rules)
.map(input -> LOOKUP.get(recipe.getType())
.getSecond()
.create(recipe.getId(), recipe.getGroup(), input, recipe.getRecipeOutput(), recipe.getExperience(), recipe.getCookTime()));
}

}
Expand Up @@ -3,10 +3,9 @@
import com.blamejared.crafttweaker.api.item.IIngredient;
import com.blamejared.crafttweaker.api.managers.IRecipeManager;
import com.blamejared.crafttweaker.api.recipes.IRecipeHandler;
import com.blamejared.crafttweaker.api.recipes.ReplacementRule;
import com.blamejared.crafttweaker.api.recipes.IReplacementRule;
import com.blamejared.crafttweaker.api.util.StringUtils;
import com.blamejared.crafttweaker.impl.item.MCItemStackMutable;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.StonecuttingRecipe;

import java.util.List;
Expand All @@ -27,19 +26,9 @@ public String dumpToCommandString(final IRecipeManager manager, final Stonecutti
}

@Override
public Optional<StonecuttingRecipe> replaceIngredients(final IRecipeManager manager, final StonecuttingRecipe recipe, final List<ReplacementRule> rules) {

final Ingredient originalInput = recipe.getIngredients().get(0);
/*mutable*/ Ingredient input = originalInput;
for (final ReplacementRule rule : rules) {
if (rule.shouldReplace(input)) {
input = rule.getVanillaTo();
}
}

if (originalInput == input) return Optional.empty();

return Optional.of(new StonecuttingRecipe(recipe.getId(), recipe.getGroup(), input, recipe.getRecipeOutput()));
public Optional<StonecuttingRecipe> replaceIngredients(final IRecipeManager manager, final StonecuttingRecipe recipe, final List<IReplacementRule<?>> rules) {
return IRecipeHandler.attemptReplacing(recipe.getIngredients().get(0), rules)
.map(input -> new StonecuttingRecipe(recipe.getId(), recipe.getGroup(), input, recipe.getRecipeOutput()));
}

}

0 comments on commit 94501b6

Please sign in to comment.