From d48538039a9fa51f17a82898ddc2b852426d77db Mon Sep 17 00:00:00 2001 From: mezz Date: Wed, 28 Aug 2024 11:06:27 +0900 Subject: [PATCH] Add more protections against invalid ITypedIngredient being created --- .../jei/common/input/ClickableIngredient.java | 12 +++++++++ .../jei/api/ingredients/ITypedIngredient.java | 2 ++ .../main/java/mezz/jei/api/recipe/IFocus.java | 2 ++ .../jei/api/runtime/IClickableIngredient.java | 18 +++++++++++++ .../jei/api/runtime/IIngredientManager.java | 4 +-- .../jei/gui/input/GuiContainerWrapper.java | 25 +++++++++++++++---- .../DisplayIngredientAcceptor.java | 9 ++++++- .../ingredients/IngredientManager.java | 3 +-- .../library/plugins/debug/JeiDebugPlugin.java | 12 +++++++++ .../vanilla/ingredients/ItemStackHelper.java | 6 ++++- .../jei/neoforge/platform/FluidHelper.java | 2 +- gradle.properties | 2 +- 12 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Common/src/main/java/mezz/jei/common/input/ClickableIngredient.java b/Common/src/main/java/mezz/jei/common/input/ClickableIngredient.java index 3952b8555..cb81894f5 100644 --- a/Common/src/main/java/mezz/jei/common/input/ClickableIngredient.java +++ b/Common/src/main/java/mezz/jei/common/input/ClickableIngredient.java @@ -1,5 +1,6 @@ package mezz.jei.common.input; +import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.runtime.IClickableIngredient; import mezz.jei.common.util.ErrorUtil; @@ -16,11 +17,22 @@ public ClickableIngredient(ITypedIngredient value, ImmutableRect2i area) { this.area = area; } + @SuppressWarnings("removal") @Override public ITypedIngredient getTypedIngredient() { return value; } + @Override + public IIngredientType getIngredientType() { + return value.getType(); + } + + @Override + public V getIngredient() { + return value.getIngredient(); + } + @Override public Rect2i getArea() { return area.toMutable(); diff --git a/CommonApi/src/main/java/mezz/jei/api/ingredients/ITypedIngredient.java b/CommonApi/src/main/java/mezz/jei/api/ingredients/ITypedIngredient.java index e70af605d..b662e52b6 100644 --- a/CommonApi/src/main/java/mezz/jei/api/ingredients/ITypedIngredient.java +++ b/CommonApi/src/main/java/mezz/jei/api/ingredients/ITypedIngredient.java @@ -3,6 +3,7 @@ import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.runtime.IIngredientManager; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.ApiStatus; import java.util.Optional; @@ -14,6 +15,7 @@ * * @since 9.3.0 */ +@ApiStatus.NonExtendable public interface ITypedIngredient { /** * @return the type of this ingredient diff --git a/CommonApi/src/main/java/mezz/jei/api/recipe/IFocus.java b/CommonApi/src/main/java/mezz/jei/api/recipe/IFocus.java index c559ae9ac..e5a0b36e6 100644 --- a/CommonApi/src/main/java/mezz/jei/api/recipe/IFocus.java +++ b/CommonApi/src/main/java/mezz/jei/api/recipe/IFocus.java @@ -2,6 +2,7 @@ import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; +import org.jetbrains.annotations.ApiStatus; import java.util.Optional; @@ -14,6 +15,7 @@ * * Use a null IFocus to signify no focus, like in the case of looking up categories of recipes. */ +@ApiStatus.NonExtendable public interface IFocus { /** * The ingredient that is being focused on. diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/IClickableIngredient.java b/CommonApi/src/main/java/mezz/jei/api/runtime/IClickableIngredient.java index 3a39d4ed0..6fdab6c10 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/IClickableIngredient.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/IClickableIngredient.java @@ -1,5 +1,6 @@ package mezz.jei.api.runtime; +import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; import net.minecraft.client.renderer.Rect2i; @@ -16,9 +17,26 @@ public interface IClickableIngredient { * Get the typed ingredient that can be looked up by JEI for recipes. * * @since 11.5.0 + * @deprecated use {@link #getIngredient()} and {@link #getIngredientType()} instead. */ + @Deprecated(since = "19.12.0", forRemoval = true) ITypedIngredient getTypedIngredient(); + /** + * @since 19.12.0 + */ + default IIngredientType getIngredientType() { + return getTypedIngredient().getType(); + } + + /** + * @since 19.12.0 + */ + default T getIngredient() { + ITypedIngredient typedIngredient = getTypedIngredient(); + return typedIngredient.getIngredient(); + } + /** * Get the area that this clickable ingredient is drawn in, in absolute screen coordinates. * This is used for click handling, to ensure the mouse-down and mouse-up are on the same ingredient. diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientManager.java b/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientManager.java index c73e6cc78..840f2325d 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientManager.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientManager.java @@ -122,8 +122,8 @@ default Collection getAllItemStacks() { /** * Create a typed ingredient, if the given ingredient is valid. * - * Invalid ingredients (according to {@link IIngredientHelper#isValidIngredient} - * cannot be created into {@link ITypedIngredient} and will instead be {@link Optional#empty()}. + * Invalid ingredients (according to {@link IIngredientHelper#isValidIngredient}) + * cannot be used in {@link ITypedIngredient} and will instead be {@link Optional#empty()}. * This helps turn all special cases like {@link ItemStack#EMPTY} into {@link Optional#empty()} instead. * * @since 11.5.0 diff --git a/Gui/src/main/java/mezz/jei/gui/input/GuiContainerWrapper.java b/Gui/src/main/java/mezz/jei/gui/input/GuiContainerWrapper.java index 84345f929..ff8c84d94 100644 --- a/Gui/src/main/java/mezz/jei/gui/input/GuiContainerWrapper.java +++ b/Gui/src/main/java/mezz/jei/gui/input/GuiContainerWrapper.java @@ -1,13 +1,18 @@ package mezz.jei.gui.input; +import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.runtime.IClickableIngredient; +import mezz.jei.api.runtime.IIngredientManager; import mezz.jei.api.runtime.IScreenHelper; +import mezz.jei.common.Internal; import mezz.jei.common.util.ImmutableRect2i; import mezz.jei.gui.overlay.elements.IElement; import mezz.jei.gui.overlay.elements.IngredientElement; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; +import java.util.Optional; import java.util.stream.Stream; public class GuiContainerWrapper implements IRecipeFocusSource { @@ -24,14 +29,24 @@ public Stream> getIngredientUnderMouse(double mo return Stream.empty(); } return screenHelper.getClickableIngredientUnderMouse(guiScreen, mouseX, mouseY) - .map(clickableSlot -> { - ITypedIngredient typedIngredient = clickableSlot.getTypedIngredient(); - ImmutableRect2i area = new ImmutableRect2i(clickableSlot.getArea()); - IElement element = new IngredientElement<>(typedIngredient); - return new ClickableIngredientInternal<>(element, area::contains, false, false); + .flatMap(clickableSlot -> { + return createTypedIngredient(clickableSlot) + .map(i -> { + ImmutableRect2i area = new ImmutableRect2i(clickableSlot.getArea()); + IElement element = new IngredientElement<>(i); + return new ClickableIngredientInternal<>(element, area::contains, false, false); + }) + .stream(); }); } + private Optional> createTypedIngredient(IClickableIngredient clickableIngredient) { + IIngredientManager ingredientManager = Internal.getJeiRuntime().getIngredientManager(); + IIngredientType ingredientType = clickableIngredient.getIngredientType(); + T ingredient = clickableIngredient.getIngredient(); + return ingredientManager.createTypedIngredient(ingredientType, ingredient); + } + @Override public Stream> getDraggableIngredientUnderMouse(double mouseX, double mouseY) { return Stream.empty(); diff --git a/Library/src/main/java/mezz/jei/library/ingredients/DisplayIngredientAcceptor.java b/Library/src/main/java/mezz/jei/library/ingredients/DisplayIngredientAcceptor.java index e0ec81b7a..73a59f326 100644 --- a/Library/src/main/java/mezz/jei/library/ingredients/DisplayIngredientAcceptor.java +++ b/Library/src/main/java/mezz/jei/library/ingredients/DisplayIngredientAcceptor.java @@ -122,7 +122,14 @@ public DisplayIngredientAcceptor addTypedIngredients(List> i public DisplayIngredientAcceptor addOptionalTypedIngredients(List>> ingredients) { ErrorUtil.checkNotNull(ingredients, "ingredients"); - this.ingredients.addAll(ingredients); + for (Optional> o : ingredients) { + if (o.isPresent()) { + this.addTypedIngredient(o.get()); + } else { + this.ingredients.add(o); + } + } + return this; } diff --git a/Library/src/main/java/mezz/jei/library/ingredients/IngredientManager.java b/Library/src/main/java/mezz/jei/library/ingredients/IngredientManager.java index 03877ec65..6c43767c4 100644 --- a/Library/src/main/java/mezz/jei/library/ingredients/IngredientManager.java +++ b/Library/src/main/java/mezz/jei/library/ingredients/IngredientManager.java @@ -156,7 +156,7 @@ public void removeIngredientsAtRuntime(IIngredientType ingredientType, Co if (!this.listeners.isEmpty()) { List> typedIngredients = ingredients.stream() - .map(i -> TypedIngredient.createUnvalidated(ingredientType, i)) + .flatMap(i -> TypedIngredient.createAndFilterInvalid(this, ingredientType, i, false).stream()) .toList(); IIngredientHelper ingredientHelper = ingredientInfo.getIngredientHelper(); @@ -185,7 +185,6 @@ public ITypedIngredient normalizeTypedIngredient(ITypedIngredient type return TypedIngredient.createUnvalidated(type, normalized); } - @SuppressWarnings("removal") @Override @Deprecated public Optional getIngredientByUid(IIngredientType ingredientType, String ingredientUuid) { diff --git a/Library/src/main/java/mezz/jei/library/plugins/debug/JeiDebugPlugin.java b/Library/src/main/java/mezz/jei/library/plugins/debug/JeiDebugPlugin.java index 0f8037510..1ac6788d8 100644 --- a/Library/src/main/java/mezz/jei/library/plugins/debug/JeiDebugPlugin.java +++ b/Library/src/main/java/mezz/jei/library/plugins/debug/JeiDebugPlugin.java @@ -9,6 +9,7 @@ import mezz.jei.api.helpers.IGuiHelper; import mezz.jei.api.helpers.IJeiHelpers; import mezz.jei.api.helpers.IPlatformFluidHelper; +import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.IIngredientTypeWithSubtypes; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.registration.IAdvancedRegistration; @@ -275,11 +276,22 @@ private record DebugClickableIngredient( Rect2i area ) implements IClickableIngredient { + @SuppressWarnings("removal") @Override public ITypedIngredient getTypedIngredient() { return typedIngredient; } + @Override + public IIngredientType getIngredientType() { + return typedIngredient.getType(); + } + + @Override + public T getIngredient() { + return typedIngredient.getIngredient(); + } + @Override public Rect2i getArea() { return area; diff --git a/Library/src/main/java/mezz/jei/library/plugins/vanilla/ingredients/ItemStackHelper.java b/Library/src/main/java/mezz/jei/library/plugins/vanilla/ingredients/ItemStackHelper.java index a6a0c311f..380a0b6e7 100644 --- a/Library/src/main/java/mezz/jei/library/plugins/vanilla/ingredients/ItemStackHelper.java +++ b/Library/src/main/java/mezz/jei/library/plugins/vanilla/ingredients/ItemStackHelper.java @@ -156,8 +156,12 @@ public ItemStack normalizeIngredient(ItemStack ingredient) { if (ingredient.getCount() == 1) { return ingredient; } + // Temporarily setting the count on the original stack this way can "recover" some empty ItemStacks. + // Copying it first results in the copy being a hard-coded ItemStack#EMPTY that cannot be recovered. + int originalCount = ingredient.getCount(); + ingredient.setCount(1); ItemStack copy = ingredient.copy(); - copy.setCount(1); + ingredient.setCount(originalCount); return copy; } diff --git a/NeoForge/src/main/java/mezz/jei/neoforge/platform/FluidHelper.java b/NeoForge/src/main/java/mezz/jei/neoforge/platform/FluidHelper.java index a3f39bbc1..a96ab8bce 100644 --- a/NeoForge/src/main/java/mezz/jei/neoforge/platform/FluidHelper.java +++ b/NeoForge/src/main/java/mezz/jei/neoforge/platform/FluidHelper.java @@ -112,7 +112,7 @@ public Optional getStillFluidSprite(FluidStack fluidStack) { @Override public Component getDisplayName(FluidStack ingredient) { Component displayName = ingredient.getHoverName(); - + Fluid fluid = ingredient.getFluid(); if (!fluid.isSource(fluid.defaultFluidState())) { return Component.translatable("jei.tooltip.liquid.flowing", displayName); diff --git a/gradle.properties b/gradle.properties index 83f8d0e9d..b58314db1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -60,4 +60,4 @@ curseHomepageUrl=https://www.curseforge.com/minecraft/mc-mods/jei jUnitVersion=5.8.2 # Version -specificationVersion=19.11.0 +specificationVersion=19.12.0