diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index 3c2f0a0d..3af8d3f9 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -779,11 +779,13 @@ bd3b6173001ed0b2203ea2335e99483b9f76c04e data/inspirations/recipes/cauldron/pota 55b751b5237c3c8dbe171f55604e7c7a94df5923 data/inspirations/recipes/cauldron/potato_soup/item.json 2cfa24cdbb1d4bc3fd15412cdb3b30eb36e338e0 data/inspirations/recipes/cauldron/potato_soup/stew_large.json ed2a7b617ee025608ece02f1bd49c6ba3ed497ad data/inspirations/recipes/cauldron/potato_soup/stew_small.json +3ffb6885e1a9c5ccc210c5e15573b3a101e5406e data/inspirations/recipes/cauldron/potion/forge_brewing.json c9de2e3800af3d019d81be1eace6d08e6deb7195 data/inspirations/recipes/cauldron/potion/lingering_bottle.json 3c4d644f1f1072d8a9ada2ff147de2b0bcf15354 data/inspirations/recipes/cauldron/potion/lingering_empty.json d9c611348b17634f9ea886a154104241a274db34 data/inspirations/recipes/cauldron/potion/lingering_fill.json 47424706c9c3dd50cc12453931821682d18a9f5c data/inspirations/recipes/cauldron/potion/normal_empty.json 49b05c1a42d06cfe853c8519028b6cfe07c2b8d9 data/inspirations/recipes/cauldron/potion/normal_fill.json +81909dc96f087ca6e01e2aad944d91ad912e5d14 data/inspirations/recipes/cauldron/potion/potion_brewing.json f8323b474fafb97b99f72e735540059420e4daf3 data/inspirations/recipes/cauldron/potion/splash_bottle.json 19f63976122e48ba4c58d5966fc3e3f88ba2633b data/inspirations/recipes/cauldron/potion/splash_empty.json c7e3e2f5fd5b1366c14b1877727c3ba4e2e3dde6 data/inspirations/recipes/cauldron/potion/splash_fill.json diff --git a/src/generated/resources/data/inspirations/recipes/cauldron/potion/forge_brewing.json b/src/generated/resources/data/inspirations/recipes/cauldron/potion/forge_brewing.json new file mode 100644 index 00000000..6aff1a09 --- /dev/null +++ b/src/generated/resources/data/inspirations/recipes/cauldron/potion/forge_brewing.json @@ -0,0 +1,13 @@ +{ + "type": "inspirations:cauldron_forge_brewing", + "conditions": [ + { + "prop": "recipes_module", + "type": "inspirations:config" + }, + { + "prop": "cauldron_brewing", + "type": "inspirations:config" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/inspirations/recipes/cauldron/potion/potion_brewing.json b/src/generated/resources/data/inspirations/recipes/cauldron/potion/potion_brewing.json new file mode 100644 index 00000000..c0a010be --- /dev/null +++ b/src/generated/resources/data/inspirations/recipes/cauldron/potion/potion_brewing.json @@ -0,0 +1,13 @@ +{ + "type": "inspirations:cauldron_potion_brewing", + "conditions": [ + { + "prop": "recipes_module", + "type": "inspirations:config" + }, + { + "prop": "cauldron_brewing", + "type": "inspirations:config" + } + ] +} \ No newline at end of file diff --git a/src/main/java/knightminer/inspirations/library/recipe/RecipeSerializers.java b/src/main/java/knightminer/inspirations/library/recipe/RecipeSerializers.java index 959413e9..83fed9ce 100644 --- a/src/main/java/knightminer/inspirations/library/recipe/RecipeSerializers.java +++ b/src/main/java/knightminer/inspirations/library/recipe/RecipeSerializers.java @@ -7,6 +7,7 @@ import knightminer.inspirations.library.recipe.cauldron.special.EmptyPotionCauldronRecipe; import knightminer.inspirations.library.recipe.cauldron.special.FillPotionCauldronRecipe; import knightminer.inspirations.library.recipe.crafting.ShapelessNoContainerRecipe; +import knightminer.inspirations.recipes.recipe.cauldron.BrewingCauldronRecipe; import knightminer.inspirations.recipes.recipe.cauldron.DyeCauldronWaterRecipe; import knightminer.inspirations.recipes.recipe.cauldron.EmptyBucketCauldronRecipe; import knightminer.inspirations.recipes.recipe.cauldron.FillBucketCauldronRecipe; @@ -47,4 +48,6 @@ public class RecipeSerializers { public static final SpecialRecipeSerializer CAULDRON_FILL_BUCKET = injected(); public static final SpecialRecipeSerializer CAULDRON_FILL_DYED_BOTTLE = injected(); public static final SpecialRecipeSerializer CAULDRON_REMOVE_BANNER_PATTERN = injected(); + public static final SpecialRecipeSerializer CAULDRON_POTION_BREWING = injected(); + public static final SpecialRecipeSerializer CAULDRON_FORGE_BREWING = injected(); } diff --git a/src/main/java/knightminer/inspirations/recipes/InspirationsRecipes.java b/src/main/java/knightminer/inspirations/recipes/InspirationsRecipes.java index ddbb6874..43f481c1 100644 --- a/src/main/java/knightminer/inspirations/recipes/InspirationsRecipes.java +++ b/src/main/java/knightminer/inspirations/recipes/InspirationsRecipes.java @@ -15,6 +15,7 @@ import knightminer.inspirations.recipes.item.EmptyBottleItem; import knightminer.inspirations.recipes.item.MixedDyedBottleItem; import knightminer.inspirations.recipes.item.SimpleDyedBottleItem; +import knightminer.inspirations.recipes.recipe.cauldron.BrewingCauldronRecipe; import knightminer.inspirations.recipes.recipe.cauldron.DyeCauldronWaterRecipe; import knightminer.inspirations.recipes.recipe.cauldron.EmptyBucketCauldronRecipe; import knightminer.inspirations.recipes.recipe.cauldron.FillBucketCauldronRecipe; @@ -204,6 +205,8 @@ void registerSerializers(Register> event) { registry.register(new SpecialRecipeSerializer<>(FillBucketCauldronRecipe::new), "cauldron_fill_bucket"); registry.register(new SpecialRecipeSerializer<>(FillDyedBottleRecipe::new), "cauldron_fill_dyed_bottle"); registry.register(new SpecialRecipeSerializer<>(RemoveBannerPatternCauldronRecipe::new), "cauldron_remove_banner_pattern"); + registry.register(new SpecialRecipeSerializer<>(BrewingCauldronRecipe.Vanilla::new), "cauldron_potion_brewing"); + registry.register(new SpecialRecipeSerializer<>(BrewingCauldronRecipe.Forge::new), "cauldron_forge_brewing"); // add water as an override to potions ICauldronContents water = CauldronContentTypes.FLUID.of(Fluids.WATER); diff --git a/src/main/java/knightminer/inspirations/recipes/data/RecipesRecipeProvider.java b/src/main/java/knightminer/inspirations/recipes/data/RecipesRecipeProvider.java index 8413cb49..64e71be8 100644 --- a/src/main/java/knightminer/inspirations/recipes/data/RecipesRecipeProvider.java +++ b/src/main/java/knightminer/inspirations/recipes/data/RecipesRecipeProvider.java @@ -128,10 +128,6 @@ private void addCauldronRecipes() { .build())) .build(cauldronRecipes, resource(folder + "empty_water_bottle")); - // TODO: remove dye recipe - // TODO: remove banner pattern - // TODO: clean shulker box - // custom recipes // @@ -412,7 +408,12 @@ private void addCauldronRecipes() { .addCriterion("has_item", hasItem(Items.DRAGON_BREATH)) .build(potionConsumer, prefix(InspirationsRecipes.lingeringBottle, potionFolder)); - // TODO: potion brewing + // brew the potions + Consumer brewingConsumer = withCondition(ConfigEnabledCondition.CAULDRON_BREWING); + CustomRecipeBuilder.customRecipe(RecipeSerializers.CAULDRON_POTION_BREWING) + .build(brewingConsumer, resourceName(potionFolder + "potion_brewing")); + CustomRecipeBuilder.customRecipe(RecipeSerializers.CAULDRON_FORGE_BREWING) + .build(brewingConsumer, resourceName(potionFolder + "forge_brewing")); // fluid recipes // // beetroot is just water based diff --git a/src/main/java/knightminer/inspirations/recipes/recipe/cauldron/BrewingCauldronRecipe.java b/src/main/java/knightminer/inspirations/recipes/recipe/cauldron/BrewingCauldronRecipe.java new file mode 100644 index 00000000..b26b7fcb --- /dev/null +++ b/src/main/java/knightminer/inspirations/recipes/recipe/cauldron/BrewingCauldronRecipe.java @@ -0,0 +1,296 @@ +package knightminer.inspirations.recipes.recipe.cauldron; + +import knightminer.inspirations.library.recipe.RecipeSerializers; +import knightminer.inspirations.library.recipe.cauldron.CauldronContentTypes; +import knightminer.inspirations.library.recipe.cauldron.inventory.ICauldronInventory; +import knightminer.inspirations.library.recipe.cauldron.inventory.IModifyableCauldronInventory; +import knightminer.inspirations.library.recipe.cauldron.recipe.ICauldronRecipe; +import knightminer.inspirations.library.recipe.cauldron.util.CauldronTemperature; +import knightminer.inspirations.library.recipe.cauldron.util.DisplayCauldronRecipe; +import knightminer.inspirations.library.util.ReflectionUtil; +import knightminer.inspirations.tweaks.recipe.NormalBrewingRecipe; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionBrewing; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.world.World; +import net.minecraftforge.common.brewing.BrewingRecipe; +import net.minecraftforge.common.brewing.BrewingRecipeRegistry; +import net.minecraftforge.common.brewing.IBrewingRecipe; +import net.minecraftforge.common.brewing.VanillaBrewingRecipe; +import slimeknights.mantle.recipe.IMultiRecipe; + +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Shared logic for both potion brewing recipe types + */ +public abstract class BrewingCauldronRecipe implements ICauldronRecipe, IMultiRecipe { + private final ResourceLocation id; + private List displayRecipes; + protected BrewingCauldronRecipe(ResourceLocation id) { + this.id = id; + } + + @Override + public boolean matches(ICauldronInventory inv, World worldIn) { + // must have liquid and not be boiling + if (inv.getLevel() == 0 || inv.getTemperature() != CauldronTemperature.BOILING) { + return false; + } + // must have an item, and if the cauldron has 2 levels we must have two of it + ItemStack stack = inv.getStack(); + if (stack.isEmpty()) { + return false; + } + // find a matching predicate and we are good + return inv.getContents() + .get(CauldronContentTypes.POTION) + .filter(value -> getResult(value, stack) != Potions.EMPTY) + .isPresent(); + } + + @Override + public void handleRecipe(IModifyableCauldronInventory inventory) { + inventory.getContents().get(CauldronContentTypes.POTION).ifPresent(potion -> { + // find a mix + ItemStack stack = inventory.getStack(); + Potion output = getResult(potion, stack); + if (output != Potions.EMPTY) { + // change the potion + inventory.setContents(CauldronContentTypes.POTION.of(output)); + + // shrink the stack in hand and return the container + ItemStack container = stack.getContainerItem().copy(); + inventory.shrinkStack(1); + inventory.setOrGiveStack(container); + + // play sound + inventory.playSound(SoundEvents.BLOCK_BREWING_STAND_BREW); + } + }); + } + + /** + * Gets the resulting potion for this combination, or empty if none + * @param potion Potion input + * @param stack Stack input + * @return Resulting potion, or empty if not recipe + */ + protected abstract Potion getResult(Potion potion, ItemStack stack); + + @Override + public ResourceLocation getId() { + return id; + } + + /** + * Makes a display brewing recipe from the given inputs + * @param input Potion input + * @param reagent Potion reagent + * @param output Potion output + * @return Recipe instance + */ + protected static DisplayCauldronRecipe makeRecipe(Potion input, Ingredient reagent, Potion output) { + return DisplayCauldronRecipe.builder(3) + .setItemInputs(Arrays.asList(reagent.getMatchingStacks())) + .setContentInputs(CauldronContentTypes.POTION.of(input)) + .setContentOutput(CauldronContentTypes.POTION.of(output)) + .build(); + } + + /** + * Gets a list of recipes for display. Does not need to be cached + * @return List of display recipes + */ + protected abstract List getDisplayRecipes(); + + @Override + public List getRecipes() { + if (displayRecipes == null) { + displayRecipes = getDisplayRecipes(); + } + return displayRecipes; + } + + /** Brewing cauldron recipe for the vanilla {@link PotionBrewing} */ + public static class Vanilla extends BrewingCauldronRecipe { + /** Last successful match among potion mixes */ + private Object lastMix; + + /** + * Creates a new recipe instance + * @param id Recipe ID + */ + public Vanilla(ResourceLocation id) { + super(id); + } + + /** + * Checks if the given mix predicate matches + * @param mix Mix predicate to test + * @param potion Potion to test against the predicate + * @param stack Reagent stack to test + * @return True if it matches + */ + private static Potion tryMix(Object mix, Potion potion, ItemStack stack) { + if (ReflectionUtil.getMixPredicateInput(mix) == potion) { + Ingredient ingredient = ReflectionUtil.getMixPredicateReagent(mix); + if (ingredient != null && ingredient.test(stack)) { + Potion output = ReflectionUtil.getMixPredicateOutput(mix); + if (output != null) { + return output; + } + } + } + return Potions.EMPTY; + } + + @Override + protected Potion getResult(Potion potion, ItemStack stack) { + // try last mix first + if (lastMix != null) { + Potion output = tryMix(lastMix, potion, stack); + if (output != Potions.EMPTY) { + return output; + } + } + + // try to find a new predicate among the list + for (Object mix : PotionBrewing.POTION_TYPE_CONVERSIONS) { + Potion output = tryMix(mix, potion, stack); + if (output != Potions.EMPTY) { + lastMix = mix; + return output; + } + } + return Potions.EMPTY; + } + + @Override + protected List getDisplayRecipes() { + // map all potion type conversions to a recipe if possible + return ((List)PotionBrewing.POTION_TYPE_CONVERSIONS).stream().flatMap(mix -> { + Potion input = ReflectionUtil.getMixPredicateInput(mix); + Potion output = ReflectionUtil.getMixPredicateOutput(mix); + Ingredient reagent = ReflectionUtil.getMixPredicateReagent(mix); + // ensure the reflection worked and the recipe is valid + if (input != null && output != null && reagent != null && !reagent.hasNoMatchingItems()) { + return Stream.of(makeRecipe(input, reagent, output)); + } + return Stream.empty(); + }).collect(Collectors.toList()); + } + + @Override + public IRecipeSerializer getSerializer() { + return RecipeSerializers.CAULDRON_POTION_BREWING; + } + } + + /** Potion brewing logic using {@link BrewingRecipeRegistry} */ + public static class Forge extends BrewingCauldronRecipe { + /** Function to make a stack from a potion */ + private static final Function POTION_ITEM_MAPPER = potion -> PotionUtils.addPotionToItemStack(new ItemStack(Items.POTION), potion); + /** Cached map of items for each potion */ + private final Map potionItemLookup = new IdentityHashMap<>(); + + /** Last successful match among potion mixes */ + private IBrewingRecipe lastRecipe; + + /** + * Creates a new recipe instance + * @param id Recipe ID + */ + public Forge(ResourceLocation id) { + super(id); + } + + /** + * Trys a potion brewing recipe to get the potion output + * @param recipe Recipe to try + * @param potion Potion stack input + * @param stack Item stack input + * @return Potion result, or empty if no match + */ + private static Potion tryRecipe(IBrewingRecipe recipe, ItemStack potion, ItemStack stack) { + ItemStack outputStack = recipe.getOutput(potion, stack); + if (!outputStack.isEmpty()) { + return PotionUtils.getPotionFromItem(outputStack); + } + return Potions.EMPTY; + } + + @Override + protected Potion getResult(Potion potion, ItemStack stack) { + // first, make a stack from the potion + ItemStack input = potionItemLookup.computeIfAbsent(potion, POTION_ITEM_MAPPER); + if (lastRecipe != null) { + Potion output = tryRecipe(lastRecipe, input, stack); + if (output != Potions.EMPTY) { + return output; + } + } + // try each brewing recipe in the registry + for (IBrewingRecipe recipe : BrewingRecipeRegistry.getRecipes()) { + // skip vanilla recipe, we handle that separately for efficiency + if (recipe instanceof VanillaBrewingRecipe) { + continue; + } + Potion output = tryRecipe(recipe, input, stack); + if (output != Potions.EMPTY) { + lastRecipe = recipe; + return output; + } + } + return Potions.EMPTY; + } + + @Override + protected List getDisplayRecipes() { + List recipes = BrewingRecipeRegistry.getRecipes(); + return Stream.concat( + // try to handle the Forge one + recipes.stream() + .filter(r -> r instanceof BrewingRecipe) + .map(r -> (BrewingRecipe) r) + .filter(r -> r.getOutput().getItem() == Items.POTION) + .flatMap(recipe -> { + // output must be a potion + Potion output = PotionUtils.getPotionFromItem(recipe.getOutput()); + if (output != Potions.EMPTY) { + // get all potion inputs and return recipes + return Arrays.stream(recipe.getInput().getMatchingStacks()) + .filter(s -> s.getItem() == Items.POTION) + .map(PotionUtils::getPotionFromItem) + .filter(pot -> pot != Potions.EMPTY) + .map(input -> makeRecipe(input, recipe.getIngredient(), output)); + } + return Stream.empty(); + }), + // special case our own brewing class, the Forge one really sucks + recipes.stream().filter(r -> r instanceof NormalBrewingRecipe) + .map(r -> (NormalBrewingRecipe) r) + .filter(NormalBrewingRecipe::isEnabled) + .map(r -> makeRecipe(r.getStart(), r.getCatalyst(), r.getOutput())) + ).collect(Collectors.toList()); + } + + @Override + public IRecipeSerializer getSerializer() { + return RecipeSerializers.CAULDRON_FORGE_BREWING; + } + } +} diff --git a/src/main/java/knightminer/inspirations/tweaks/recipe/NormalBrewingRecipe.java b/src/main/java/knightminer/inspirations/tweaks/recipe/NormalBrewingRecipe.java index 68b41036..243690c0 100644 --- a/src/main/java/knightminer/inspirations/tweaks/recipe/NormalBrewingRecipe.java +++ b/src/main/java/knightminer/inspirations/tweaks/recipe/NormalBrewingRecipe.java @@ -60,11 +60,14 @@ public boolean isIngredient(ItemStack ingredient) { @Override public ItemStack getOutput(ItemStack input, ItemStack ingredient) { + if (!isEnabled()) { + return ItemStack.EMPTY; + } if (!catalyst.test(ingredient)) { return ItemStack.EMPTY; } Item item = input.getItem(); - if (item == Items.POTION || item == Items.SPLASH_POTION || item == Items.LINGERING_POTION) { + if (isInput(input)) { return PotionUtils.addPotionToItemStack(new ItemStack(item), output); } return ItemStack.EMPTY;