Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding PredicateChoice to Paper API (updated version) #12017

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.bukkit.inventory;

import com.google.common.base.Preconditions;
import org.jspecify.annotations.NullMarked;
import java.util.function.Predicate;

@NullMarked
record PredicateChoiceImpl(Predicate<ItemStack> stackPredicate, ItemStack exampleStack) implements RecipeChoice.PredicateChoice {

public PredicateChoiceImpl(Predicate<ItemStack> stackPredicate, ItemStack exampleStack) {
Preconditions.checkArgument(stackPredicate != null, "The item predicate cannot be null");
Preconditions.checkArgument(exampleStack != null, "The example stack cannot be null");
Preconditions.checkArgument(!exampleStack.isEmpty(), "Cannot have empty/air example stack");

this.stackPredicate = stackPredicate;
this.exampleStack = exampleStack.clone();
}
CPieter marked this conversation as resolved.
Show resolved Hide resolved

@Override
public Predicate<ItemStack> getPredicate() {
return this.stackPredicate;
}

@Override
public ItemStack getItemStack() {
return this.exampleStack.clone();
}

@Override
public PredicateChoiceImpl clone() {
return new PredicateChoiceImpl(this.stackPredicate::test, this.exampleStack.clone());
}

@Override
public boolean test(final ItemStack itemStack) {
return this.stackPredicate.test(itemStack);
}
}
63 changes: 63 additions & 0 deletions paper-api/src/main/java/org/bukkit/inventory/RecipeChoice.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.bukkit.Tag;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;

/**
* Represents a potential item match within a recipe. All choices within a
Expand All @@ -23,6 +24,7 @@
public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {

// Paper start - add "empty" choice

/**
* An "empty" recipe choice. Only valid as a recipe choice in
* specific places. Check the javadocs of a method before using it
Expand All @@ -35,6 +37,46 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
}
// Paper end

/**
* Creates a choice that will be valid only if one of the stacks is
* exactly matched (aside from stack size).
*
* @param stacks the ItemStacks to match against.
* Cannot be empty or contain empty/air stacks.
* @return a new ExactChoice.
*/
static @NotNull ExactChoice exactChoice(@NotNull ItemStack... stacks) {
CPieter marked this conversation as resolved.
Show resolved Hide resolved
return new ExactChoice(stacks);
}

/**
* Creates a choice that will be valid only if one of the stacks is
* exactly matched (aside from stack size).
*
* @param stacks the ItemStacks to match against.
* Cannot be empty or contain empty/air stacks.
* @return a new ExactChoice.
*/
static @NotNull ExactChoice exactChoice(@NotNull List<ItemStack> stacks) {
return new ExactChoice(stacks);
}

/**
* Creates a recipe choice that will be valid only if an item matches the
* given predicate.
* <p>
* <b>Note:</b> Mutating the {@link ItemStack} within the predicate is not
* supported.
*
* @param stackPredicate the predicate to match against.
* @param exampleStack an example {@link ItemStack} to be shown in the
* recipe book. Cannot be empty or air.
* @return a new PredicateChoice.
*/
static @NotNull PredicateChoice predicateChoice(@NotNull Predicate<ItemStack> stackPredicate, @NotNull ItemStack exampleStack) {
return new PredicateChoiceImpl(stackPredicate, exampleStack);
}

/**
* Gets a single item stack representative of this stack choice.
*
Expand Down Expand Up @@ -79,10 +121,12 @@ public MaterialChoice(@NotNull Material... choices) {
*
* @param choices the tag
*/
@Deprecated
public MaterialChoice(@NotNull Tag<Material> choices) {
this(new ArrayList<>(java.util.Objects.requireNonNull(choices, "Cannot create a material choice with null tag").getValues())); // Paper - delegate to list ctor to make sure all checks are called
}

@Deprecated
CPieter marked this conversation as resolved.
Show resolved Hide resolved
public MaterialChoice(@NotNull List<Material> choices) {
Preconditions.checkArgument(choices != null, "choices");
Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice");
Expand Down Expand Up @@ -192,14 +236,23 @@ public static class ExactChoice implements RecipeChoice {

private List<ItemStack> choices;

/**
* @deprecated Use {@link RecipeChoice#exactChoice(ItemStack...)} instead
*/
public ExactChoice(@NotNull ItemStack stack) {
this(Arrays.asList(stack));
}

/**
* @deprecated Use {@link RecipeChoice#exactChoice(ItemStack...)} instead
*/
public ExactChoice(@NotNull ItemStack... stacks) {
this(Arrays.asList(stacks));
}

/**
* @deprecated Use {@link RecipeChoice#exactChoice(List)} instead
*/
public ExactChoice(@NotNull List<ItemStack> choices) {
CPieter marked this conversation as resolved.
Show resolved Hide resolved
Preconditions.checkArgument(choices != null, "choices");
Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice");
Expand Down Expand Up @@ -290,4 +343,14 @@ public String toString() {
}
// Paper end - check valid ingredients
}

/**
* Represents a choice that will be valid only if an item matches the
* given predicate.
*/
@NullMarked
sealed interface PredicateChoice extends RecipeChoice permits PredicateChoiceImpl {

Predicate<ItemStack> getPredicate();
CPieter marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ index 0000000000000000000000000000000000000000..ce745e49cd54fe3ae187785563a1bd31
+}
diff --git a/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de372e25b6
index 0000000000000000000000000000000000000000..12da5da5b6a9732d74f2a9acedc7dec84eb3bfee
--- /dev/null
+++ b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
@@ -0,0 +1,68 @@
@@ -0,0 +1,80 @@
+package io.papermc.paper.inventory.recipe;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
Expand All @@ -93,12 +93,16 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
+import net.minecraft.world.item.crafting.CraftingInput;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public final class StackedContentsExtrasMap {
+
+ private final StackedContents<ItemOrExact> contents;
+ public Object2IntMap<ItemOrExact.Item> regularRemoved = new Object2IntOpenHashMap<>(); // needed for re-using the regular contents (for ShapelessRecipe)
+ public final ObjectSet<ItemStack> exactIngredients = new ObjectOpenCustomHashSet<>(ItemStackLinkedSet.TYPE_AND_TAG);
+ public final List<Predicate<ItemStack>> predicateIngredients = new ArrayList<>();
+
+ public StackedContentsExtrasMap(final StackedContents<ItemOrExact> contents) {
+ this.contents = contents;
Expand All @@ -107,7 +111,9 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
+ public void initialize(final Recipe<?> recipe) {
+ this.exactIngredients.clear();
+ for (final Ingredient ingredient : recipe.placementInfo().ingredients()) {
+ if (ingredient.isExact()) {
+ if (ingredient.stackPredicate != null) {
+ this.predicateIngredients.add(ingredient.stackPredicate);
+ } else if (ingredient.isExact()) {
+ this.exactIngredients.addAll(ingredient.itemStacks());
+ }
+ }
Expand Down Expand Up @@ -137,11 +143,17 @@ index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de
+ for (final Object2IntMap.Entry<ItemOrExact.Item> entry : this.regularRemoved.object2IntEntrySet()) {
+ this.contents.amounts.addTo(entry.getKey(), entry.getIntValue());
+ }
+ this.predicateIngredients.clear();
+ this.exactIngredients.clear();
+ this.regularRemoved.clear();
+ }
+
+ public boolean accountStack(final ItemStack stack, final int count) {
+ for (Predicate<ItemStack> stackPredicate : this.predicateIngredients) {
+ if (!stackPredicate.test(stack)) continue;
+ this.contents.account(new ItemOrExact.Exact(stack), count);
+ return true;
+ }
+ if (this.exactIngredients.contains(stack)) {
+ this.contents.account(new ItemOrExact.Exact(stack), count);
+ return true;
Expand Down Expand Up @@ -341,7 +353,7 @@ index 6bbe2e51ef71d193e0a5d3cace2b0ad1760ce759..83ccde54c625d40dc595e000c533f60a
}

diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java
index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d521bd76ba 100644
index 4ed4f4a3ee7df461ccedcf3506ccadcd27d45f94..7a3f2e45ae5dabd0e4ca65b9edf7b5df83b998fc 100644
--- a/net/minecraft/world/item/crafting/Ingredient.java
+++ b/net/minecraft/world/item/crafting/Ingredient.java
@@ -21,7 +21,7 @@ import net.minecraft.world.item.Items;
Expand All @@ -353,8 +365,8 @@ index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d5
public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
.map(Ingredient::new, ingredient -> ingredient.values);
public static final StreamCodec<RegistryFriendlyByteBuf, Optional<Ingredient>> OPTIONAL_CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
@@ -35,20 +35,24 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
private final HolderSet<Item> values;
@@ -37,20 +37,24 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
public Predicate<ItemStack> stackPredicate; // Paper - add PredicateChoice
// CraftBukkit start
@javax.annotation.Nullable
- private java.util.List<ItemStack> itemStacks;
Expand All @@ -381,8 +393,8 @@ index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d5
return recipe;
}
// CraftBukkit end
@@ -81,21 +85,22 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
public boolean test(ItemStack stack) {
@@ -88,21 +92,26 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
// Paper end - add PredicateChoice
// CraftBukkit start
if (this.isExact()) {
- for (ItemStack itemstack1 : this.itemStacks()) {
Expand All @@ -406,14 +418,18 @@ index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d5
+ return switch (itemOrExact) {
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Item(final Holder<Item> item) ->
+ !this.isExact() && this.values.contains(item);
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Exact(final ItemStack exact) ->
+ this.isExact() && this.itemStacks.contains(exact);
+ case io.papermc.paper.inventory.recipe.ItemOrExact.Exact(final ItemStack exact) -> {
+ if (this.stackPredicate != null) {
+ yield this.stackPredicate.test(exact);
+ }
+ yield this.isExact() && this.itemStacks.contains(exact);
+ }
+ };
+ // Paper end - Improve exact choice recipe ingredients
}

@Override
@@ -120,6 +125,11 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
@@ -127,6 +136,11 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
}

public SlotDisplay display() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
--- a/net/minecraft/world/item/crafting/Ingredient.java
+++ b/net/minecraft/world/item/crafting/Ingredient.java
@@ -33,6 +_,25 @@
@@ -33,6 +_,27 @@
public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC)
.xmap(Ingredient::new, ingredient -> ingredient.values);
private final HolderSet<Item> values;
+ @org.jetbrains.annotations.Nullable
+ public Predicate<ItemStack> stackPredicate; // Paper - add PredicateChoice
+ // CraftBukkit start
+ @javax.annotation.Nullable
+ private java.util.List<ItemStack> itemStacks;
Expand All @@ -26,10 +28,15 @@

private Ingredient(HolderSet<Item> values) {
values.unwrap().ifRight(list -> {
@@ -60,6 +_,17 @@
@@ -60,6 +_,22 @@

@Override
public boolean test(ItemStack stack) {
+ // Paper start - add PredicateChoice
+ if (this.stackPredicate != null) {
+ return this.stackPredicate.test(stack);
+ }
+ // Paper end - add PredicateChoice
+ // CraftBukkit start
+ if (this.isExact()) {
+ for (ItemStack itemstack1 : this.itemStacks()) {
Expand Down
CPieter marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
Expand All @@ -32,6 +35,9 @@ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) {

if (bukkit == null) {
stack = Ingredient.of();
} else if (bukkit instanceof RecipeChoice.PredicateChoice predicateChoice) {
stack = Ingredient.ofStacks(Collections.singletonList(CraftItemStack.asNMSCopy(predicateChoice.getItemStack())));
stack.stackPredicate = nmsStack -> predicateChoice.test(CraftItemStack.asBukkitCopy(nmsStack));
} else if (bukkit instanceof RecipeChoice.MaterialChoice) {
stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat)));
} else if (bukkit instanceof RecipeChoice.ExactChoice) {
Expand Down