Skip to content

Add RecipeChoice.ItemTypeChoice #12049

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.bukkit.inventory;

import com.google.common.base.Preconditions;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.keys.ItemTypeKeys;
import io.papermc.paper.registry.set.RegistryKeySet;
import org.jspecify.annotations.NullMarked;

@NullMarked
record ItemTypeRecipeChoiceImpl(RegistryKeySet<ItemType> itemTypes) implements RecipeChoice.ItemTypeChoice {

ItemTypeRecipeChoiceImpl {
Preconditions.checkArgument(!itemTypes.isEmpty(), "ItemTypeChoice cannot be empty");
Preconditions.checkArgument(!(itemTypes.contains(ItemTypeKeys.AIR)), "ItemTypeChoice cannot contain minecraft:air");
}

@Override
public ItemStack getItemStack() {
throw new UnsupportedOperationException("ItemTypeChoice does not support this");
}

@Override
public RecipeChoice clone() {
return new ItemTypeRecipeChoiceImpl(this.itemTypes);
}

@Override
public boolean test(final ItemStack itemStack) {
return this.itemTypes.contains(RegistryKey.ITEM.typedKey(itemStack.getType().key()));
}

@Override
public RecipeChoice validate(final boolean allowEmptyRecipes) {
Preconditions.checkArgument(!(this.itemTypes.contains(ItemTypeKeys.AIR)), "ItemTypeChoice cannot contain minecraft:air");
return this;
}
}
83 changes: 56 additions & 27 deletions paper-api/src/main/java/org/bukkit/inventory/RecipeChoice.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.bukkit.inventory;

import com.google.common.base.Preconditions;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.set.RegistryKeySet;
import io.papermc.paper.registry.tag.TagKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -11,7 +15,9 @@
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;

/**
* Represents a potential item match within a recipe. All choices within a
Expand All @@ -20,20 +26,34 @@
*
* <b>This class is not legal for implementation by plugins!</b>
*/
@NullMarked
@ApiStatus.NonExtendable
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
* to be sure it's valid for that recipe and ingredient type.
*
* @return the empty recipe choice
*/
static @NotNull RecipeChoice empty() {
static RecipeChoice empty() {
return EmptyRecipeChoice.INSTANCE;
}
// Paper end

/**
* Creates a new recipe choice based on a {@link RegistryKeySet} of item types.
* Can either be created via {@link RegistryKeySet#keySet(RegistryKey, TypedKey[])}
* or obtained from {@link org.bukkit.Registry#getTag(TagKey)}.
*
* @param itemTypes the item types to match
* @return a new recipe choice
*/
@Contract(pure = true, value = "_ -> new")
@ApiStatus.Experimental
static ItemTypeChoice itemType(final RegistryKeySet<ItemType> itemTypes) {
return new ItemTypeRecipeChoiceImpl(itemTypes);
}

/**
* Gets a single item stack representative of this stack choice.
Expand All @@ -42,34 +62,32 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
* @deprecated for compatibility only
*/
@Deprecated(since = "1.13.1")
@NotNull
ItemStack getItemStack();

@NotNull
RecipeChoice clone();

@Override
boolean test(@NotNull ItemStack itemStack);
boolean test(ItemStack itemStack);

// Paper start - check valid ingredients
@org.jetbrains.annotations.ApiStatus.Internal
default @NotNull RecipeChoice validate(final boolean allowEmptyRecipes) {
default RecipeChoice validate(final boolean allowEmptyRecipes) {
return this;
}
// Paper end - check valid ingredients

/**
* Represents a choice of multiple matching Materials.
*/
public static class MaterialChoice implements RecipeChoice {
final class MaterialChoice implements RecipeChoice {

private List<Material> choices;

public MaterialChoice(@NotNull Material choice) {
public MaterialChoice(Material choice) {
this(Arrays.asList(choice));
}

public MaterialChoice(@NotNull Material... choices) {
public MaterialChoice(Material... choices) {
this(Arrays.asList(choices));
}

Expand All @@ -79,11 +97,11 @@ public MaterialChoice(@NotNull Material... choices) {
*
* @param choices the tag
*/
public MaterialChoice(@NotNull Tag<Material> choices) {
public MaterialChoice(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
}

public MaterialChoice(@NotNull List<Material> choices) {
public MaterialChoice(List<Material> choices) {
Preconditions.checkArgument(choices != null, "choices");
Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice");

Expand All @@ -103,7 +121,7 @@ public MaterialChoice(@NotNull List<Material> choices) {
}

@Override
public boolean test(@NotNull ItemStack t) {
public boolean test(ItemStack t) {
for (Material match : choices) {
if (t.getType() == match) {
return true;
Expand All @@ -113,7 +131,6 @@ public boolean test(@NotNull ItemStack t) {
return false;
}

@NotNull
@Override
public ItemStack getItemStack() {
ItemStack stack = new ItemStack(choices.get(0));
Expand All @@ -126,12 +143,10 @@ public ItemStack getItemStack() {
return stack;
}

@NotNull
public List<Material> getChoices() {
return Collections.unmodifiableList(choices);
}

@NotNull
@Override
public MaterialChoice clone() {
try {
Expand Down Expand Up @@ -175,7 +190,7 @@ public String toString() {

// Paper start - check valid ingredients
@Override
public @NotNull RecipeChoice validate(final boolean allowEmptyRecipes) {
public RecipeChoice validate(final boolean allowEmptyRecipes) {
if (this.choices.stream().anyMatch(Material::isAir)) {
throw new IllegalArgumentException("RecipeChoice.MaterialChoice cannot contain air");
}
Expand All @@ -188,19 +203,19 @@ public String toString() {
* Represents a choice that will be valid only if one of the stacks is
* exactly matched (aside from stack size).
*/
public static class ExactChoice implements RecipeChoice {
final class ExactChoice implements RecipeChoice {

private List<ItemStack> choices;

public ExactChoice(@NotNull ItemStack stack) {
public ExactChoice(ItemStack stack) {
this(Arrays.asList(stack));
}

public ExactChoice(@NotNull ItemStack... stacks) {
public ExactChoice(ItemStack... stacks) {
this(Arrays.asList(stacks));
}

public ExactChoice(@NotNull List<ItemStack> choices) {
public ExactChoice(List<ItemStack> choices) {
Preconditions.checkArgument(choices != null, "choices");
Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice");
for (ItemStack choice : choices) {
Expand All @@ -211,18 +226,15 @@ public ExactChoice(@NotNull List<ItemStack> choices) {
this.choices = new ArrayList<>(choices);
}

@NotNull
@Override
public ItemStack getItemStack() {
return choices.get(0).clone();
}

@NotNull
public List<ItemStack> getChoices() {
return Collections.unmodifiableList(choices);
}

@NotNull
@Override
public ExactChoice clone() {
try {
Expand All @@ -240,7 +252,7 @@ public ExactChoice clone() {
}

@Override
public boolean test(@NotNull ItemStack t) {
public boolean test(ItemStack t) {
for (ItemStack match : choices) {
if (t.isSimilar(match)) {
return true;
Expand Down Expand Up @@ -282,12 +294,29 @@ public String toString() {

// Paper start - check valid ingredients
@Override
public @NotNull RecipeChoice validate(final boolean allowEmptyRecipes) {
public RecipeChoice validate(final boolean allowEmptyRecipes) {
if (this.choices.stream().anyMatch(s -> s.getType().isAir())) {
throw new IllegalArgumentException("RecipeChoice.ExactChoice cannot contain air");
}
return this;
}
// Paper end - check valid ingredients
}

/**
* Represents a choice that will be valid if the {@link ItemStack#getType()}
* matches any of the item types in the set.
* @see #itemType(RegistryKeySet)
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
sealed interface ItemTypeChoice extends RecipeChoice permits ItemTypeRecipeChoiceImpl {

/**
* Gets the set of item types that this choice will match.
*
* @return the set of item types
*/
RegistryKeySet<ItemType> itemTypes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,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 ac4520afe510df9b1dd256d13dcb0768c83edb70..da95f40503f3696c8daa8fd0e947508fe43060ba 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 @@ -354,7 +354,7 @@ index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d5
.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;
public final HolderSet<Item> values;
// CraftBukkit start
@javax.annotation.Nullable
- private java.util.List<ItemStack> itemStacks;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--- a/net/minecraft/core/HolderSet.java
+++ b/net/minecraft/core/HolderSet.java
@@ -166,6 +_,8 @@
private final TagKey<T> key;
@Nullable
private List<Holder<T>> contents;
+ @Nullable
+ private Set<io.papermc.paper.registry.TypedKey<?>> typedKeys; // Paper - cache typed key set for constant contains calls

Named(HolderOwner<T> owner, TagKey<T> key) {
this.owner = owner;
@@ -174,6 +_,7 @@

void bind(List<Holder<T>> contents) {
this.contents = List.copyOf(contents);
+ this.typedKeys = null; // Paper - reset if tag is re-bound
}

public TagKey<T> key() {
@@ -218,5 +_,15 @@
public boolean canSerializeIn(HolderOwner<T> owner) {
return this.owner.canSerializeIn(owner);
}
+
+ // Paper start - cache typed key set for constant contains calls
+ public boolean contains(io.papermc.paper.registry.TypedKey<?> key) {
+ if (this.typedKeys == null) {
+ this.typedKeys = this.contents().stream().map(h -> io.papermc.paper.registry.PaperRegistries.fromNms(h.unwrapKey().orElseThrow())).collect(java.util.stream.Collectors.toUnmodifiableSet());
+ }
+
+ return this.typedKeys.contains(key);
+ }
+ // Paper end
}
}
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 @@
@@ -32,7 +_,26 @@
public static final Codec<HolderSet<Item>> NON_AIR_HOLDER_SET_CODEC = HolderSetCodec.create(Registries.ITEM, Item.CODEC, false);
public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC)
.xmap(Ingredient::new, ingredient -> ingredient.values);
private final HolderSet<Item> values;
- private final HolderSet<Item> values;
+ public final HolderSet<Item> values;
+ // CraftBukkit start
+ @javax.annotation.Nullable
+ private java.util.List<ItemStack> itemStacks;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ public RegistryKey<T> registryKey() {

@Override
public boolean contains(final TypedKey<T> valueKey) {
return Iterables.any(this.namedSet, h -> {
return PaperRegistries.fromNms(((Holder.Reference<?>) h).key()).equals(valueKey);
});
return this.namedSet.contains(valueKey);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.bukkit.craftbukkit.inventory;

import com.google.common.base.Preconditions;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand All @@ -9,6 +13,7 @@
import net.minecraft.world.item.crafting.Ingredient;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.recipe.CookingBookCategory;
Expand All @@ -32,6 +37,8 @@ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) {

if (bukkit == null) {
stack = Ingredient.of();
} else if (bukkit instanceof final RecipeChoice.ItemTypeChoice itemTypeChoice) {
stack = Ingredient.of(PaperRegistrySets.convertToNms(Registries.ITEM, Conversions.global().lookup(), itemTypeChoice.itemTypes()));
} 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 Expand Up @@ -70,6 +77,9 @@ public static RecipeChoice toBukkit(Ingredient list) {
}

return new RecipeChoice.ExactChoice(choices);
// } else { // TODO change over to this when it's not experimental
// RegistryKeySet<ItemType> itemTypes = PaperRegistrySets.convertToApi(RegistryKey.ITEM, list.values);
// return RecipeChoice.itemType(itemTypes);
} else {
List<org.bukkit.Material> choices = list.items().map((i) -> CraftItemType.minecraftToBukkit(i.value())).toList();

Expand Down
Loading