diff --git a/src/accessors/resources/mixins.sponge.accessors.json b/src/accessors/resources/mixins.sponge.accessors.json index 842d03e32d9..948b8a2902b 100644 --- a/src/accessors/resources/mixins.sponge.accessors.json +++ b/src/accessors/resources/mixins.sponge.accessors.json @@ -145,7 +145,6 @@ "world.item.component.CustomDataAccessor", "world.item.context.UseOnContextAccessor", "world.item.crafting.RecipeMapAccessor", - "world.item.crafting.ShapelessRecipeAccessor", "world.item.enchantment.ItemEnchantmentsAccessor", "world.item.trading.MerchantOfferAccessor", "world.level.BaseCommandBlockAccessor", diff --git a/src/main/java/org/spongepowered/common/bridge/world/item/crafting/CraftingInputBridge.java b/src/main/java/org/spongepowered/common/bridge/world/item/crafting/CraftingInputBridge.java new file mode 100644 index 00000000000..6f06fe07c63 --- /dev/null +++ b/src/main/java/org/spongepowered/common/bridge/world/item/crafting/CraftingInputBridge.java @@ -0,0 +1,33 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.bridge.world.item.crafting; + +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; + +public interface CraftingInputBridge { + + StackedContents bridge$getItemStackStackedContents(); +} diff --git a/src/accessors/java/org/spongepowered/common/accessor/world/item/crafting/ShapelessRecipeAccessor.java b/src/main/java/org/spongepowered/common/bridge/world/item/crafting/PlacementInfoBridge.java similarity index 75% rename from src/accessors/java/org/spongepowered/common/accessor/world/item/crafting/ShapelessRecipeAccessor.java rename to src/main/java/org/spongepowered/common/bridge/world/item/crafting/PlacementInfoBridge.java index b0343958027..afad5f5a20e 100644 --- a/src/accessors/java/org/spongepowered/common/accessor/world/item/crafting/ShapelessRecipeAccessor.java +++ b/src/main/java/org/spongepowered/common/bridge/world/item/crafting/PlacementInfoBridge.java @@ -22,16 +22,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.common.accessor.world.item.crafting; +package org.spongepowered.common.bridge.world.item.crafting; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.ShapelessRecipe; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; import java.util.List; -@Mixin(ShapelessRecipe.class) -public interface ShapelessRecipeAccessor { - @Accessor("ingredients") List accessor$ingredients(); +public interface PlacementInfoBridge { + + List> bridge$getStackIngredientInfos(); } diff --git a/src/main/java/org/spongepowered/common/item/recipe/crafting/shapeless/SpongeShapelessRecipe.java b/src/main/java/org/spongepowered/common/item/recipe/crafting/shapeless/SpongeShapelessRecipe.java index 7981d28781e..11a8752d5c2 100644 --- a/src/main/java/org/spongepowered/common/item/recipe/crafting/shapeless/SpongeShapelessRecipe.java +++ b/src/main/java/org/spongepowered/common/item/recipe/crafting/shapeless/SpongeShapelessRecipe.java @@ -32,17 +32,11 @@ import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.ShapelessRecipe; import net.minecraft.world.level.Level; -import org.spongepowered.common.accessor.world.item.crafting.ShapelessRecipeAccessor; +import org.spongepowered.common.bridge.world.item.crafting.CraftingInputBridge; +import org.spongepowered.common.bridge.world.item.crafting.PlacementInfoBridge; import org.spongepowered.common.item.recipe.ingredient.SpongeIngredient; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.Function; /** @@ -68,11 +62,19 @@ public SpongeShapelessRecipe(final String groupIn, } @Override - public boolean matches(final CraftingInput $$0, final Level $$1) { + public boolean matches(final CraftingInput input, final Level $$1) { if (this.onlyVanillaIngredients) { - return super.matches($$0, $$1); + return super.matches(input, $$1); } - return SpongeShapelessRecipe.matches($$0.items(), ((ShapelessRecipeAccessor) this).accessor$ingredients()); + + // Quick check to avoid complex calculations if possible + if (input.ingredientCount() != this.placementInfo().ingredients().size()) { + // The amount of non-empty stacks doesn't match the amount of ingredients + return false; + } + + return ((CraftingInputBridge) input).bridge$getItemStackStackedContents().tryPick( + ((PlacementInfoBridge) this.placementInfo()).bridge$getStackIngredientInfos(), 1, null); } @Override @@ -91,60 +93,4 @@ public ItemStack assemble(final CraftingInput $$0, final HolderLookup.Provider $ } return super.assemble($$0, $$1); } - - private static boolean - matches(List stacks, List ingredients) { - final int elements = ingredients.size(); - if (stacks.size() < elements) { - return false; - } - - // find matched stack -> ingredient list - final Map> matchesMap = new HashMap<>(); - for (int i = 0; i < ingredients.size(); i++) { - Ingredient ingredient = ingredients.get(i); - boolean noMatch = true; - for (int j = 0; j < stacks.size(); j++) { - if (ingredient.test(stacks.get(j))) { - matchesMap.computeIfAbsent(j, k -> new ArrayList<>()).add(i);; - noMatch = false; - } - } - if (noMatch) { - // one ingredient had no match recipe does not match at all - return false; - } - } - - if (matchesMap.isEmpty()) { - return false; - } - - // Every ingredient had at least one matching stack - // Now check if each stack matches one ingredient - final List> stackList = new ArrayList<>(matchesMap.values()); - stackList.sort(Comparator.comparingInt(Collection::size)); - return SpongeShapelessRecipe.matchesRecursive(stackList, 0, new HashSet<>()); - } - - private static boolean matchesRecursive(List> stackList, int d, Set used) { - if (d == stackList.size()) { - return true; - } - - final Collection stacks = stackList.get(d); - for (Integer stack : stacks) { - if (used.contains(stack)) { - // each stack is only used once - continue; - } - final HashSet copy = new HashSet<>(used); - copy.add(stack); - if (SpongeShapelessRecipe.matchesRecursive(stackList, d + 1, copy)) { - return true; - } - } - return false; - } - } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/CraftingInputMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/CraftingInputMixin.java new file mode 100644 index 00000000000..bc2ceb737cd --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/CraftingInputMixin.java @@ -0,0 +1,59 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.item.crafting; + +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingInput; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.common.bridge.world.item.crafting.CraftingInputBridge; + +import java.util.List; + +@Mixin(CraftingInput.class) +public abstract class CraftingInputMixin implements CraftingInputBridge { + + @Shadow @Final private List items; + + private @MonotonicNonNull StackedContents impl$itemStackStackedContents; + + @Override + public StackedContents bridge$getItemStackStackedContents() { + // This is only called by sponge recipes in some cases + // so we don't need to calculate it for each CraftingInput + if (this.impl$itemStackStackedContents == null) { + this.impl$itemStackStackedContents = new StackedContents<>(); + for (final ItemStack stack : this.items) { + if (!stack.isEmpty()) { + this.impl$itemStackStackedContents.account(stack, 1); + } + } + } + return this.impl$itemStackStackedContents; + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/PlacementInfoMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/PlacementInfoMixin.java new file mode 100644 index 00000000000..00a0beedae4 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/crafting/PlacementInfoMixin.java @@ -0,0 +1,58 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.item.crafting; + +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.PlacementInfo; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.common.bridge.world.item.crafting.PlacementInfoBridge; + +import java.util.List; + +@Mixin(PlacementInfo.class) +public abstract class PlacementInfoMixin implements PlacementInfoBridge { + + @Shadow @Final private List ingredients; + + private List> impl$stackIngredientInfos; + + @Inject(method = "", at = @At("RETURN")) + private void impl$setIngredientInfos(final CallbackInfo ci) { + this.impl$stackIngredientInfos = this.ingredients.stream() + .>map(ingredient -> ingredient::test) + .toList(); + } + + public List> bridge$getStackIngredientInfos() { + return this.impl$stackIngredientInfos; + } +} diff --git a/src/mixins/resources/mixins.sponge.core.json b/src/mixins/resources/mixins.sponge.core.json index e850c287944..1f5af9717ef 100644 --- a/src/mixins/resources/mixins.sponge.core.json +++ b/src/mixins/resources/mixins.sponge.core.json @@ -210,7 +210,9 @@ "world.item.ServerItemCooldownsMixin", "world.item.TeleportRandomlyConsumeEffectMixin", "world.item.crafting.AbstractCookingRecipeMixin", + "world.item.crafting.CraftingInputMixin", "world.item.crafting.IngredientMixin", + "world.item.crafting.PlacementInfoMixin", "world.item.crafting.RecipeManagerMixin", "world.item.crafting.ShapedRecipeMixin", "world.item.crafting.ShapelessRecipeMixin",