Skip to content

Commit 350514c

Browse files
committed
Add RecipeChoice.ItemTypeChoice
1 parent d4a9578 commit 350514c

File tree

7 files changed

+145
-34
lines changed

7 files changed

+145
-34
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.bukkit.inventory;
2+
3+
import com.google.common.base.Preconditions;
4+
import io.papermc.paper.registry.RegistryKey;
5+
import io.papermc.paper.registry.keys.ItemTypeKeys;
6+
import io.papermc.paper.registry.set.RegistryKeySet;
7+
import org.jspecify.annotations.NullMarked;
8+
9+
@NullMarked
10+
record ItemTypeRecipeChoiceImpl(RegistryKeySet<ItemType> itemTypes) implements RecipeChoice.ItemTypeChoice {
11+
12+
ItemTypeRecipeChoiceImpl {
13+
Preconditions.checkArgument(!itemTypes.isEmpty(), "RecipeChoice.ItemTypeChoice cannot be empty");
14+
Preconditions.checkArgument(!(itemTypes.contains(ItemTypeKeys.AIR)), "RecipeChoice.ItemTypeChoice cannot contain minecraft:air");
15+
}
16+
17+
@Override
18+
public ItemStack getItemStack() {
19+
throw new UnsupportedOperationException("ItemTypeRecipeChoice does not support this");
20+
}
21+
22+
@Override
23+
public RecipeChoice clone() {
24+
return new ItemTypeRecipeChoiceImpl(this.itemTypes);
25+
}
26+
27+
@Override
28+
public boolean test(final ItemStack itemStack) {
29+
return this.itemTypes.contains(RegistryKey.ITEM.typedKey(itemStack.getType().key()));
30+
}
31+
32+
@Override
33+
public RecipeChoice validate(final boolean allowEmptyRecipes) {
34+
Preconditions.checkArgument(!(this.itemTypes.contains(ItemTypeKeys.AIR)), "RecipeChoice.ItemTypeChoice cannot contain minecraft:air");
35+
return this;
36+
}
37+
}

paper-api/src/main/java/org/bukkit/inventory/RecipeChoice.java

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.bukkit.inventory;
22

33
import com.google.common.base.Preconditions;
4+
import io.papermc.paper.registry.RegistryKey;
5+
import io.papermc.paper.registry.TypedKey;
6+
import io.papermc.paper.registry.set.RegistryKeySet;
7+
import io.papermc.paper.registry.tag.TagKey;
48
import java.util.ArrayList;
59
import java.util.Arrays;
610
import java.util.Collections;
@@ -11,7 +15,9 @@
1115
import org.bukkit.Material;
1216
import org.bukkit.Tag;
1317
import org.bukkit.material.MaterialData;
14-
import org.jetbrains.annotations.NotNull;
18+
import org.jetbrains.annotations.ApiStatus;
19+
import org.jetbrains.annotations.Contract;
20+
import org.jspecify.annotations.NullMarked;
1521

1622
/**
1723
* Represents a potential item match within a recipe. All choices within a
@@ -20,20 +26,34 @@
2026
*
2127
* <b>This class is not legal for implementation by plugins!</b>
2228
*/
29+
@NullMarked
30+
@ApiStatus.NonExtendable
2331
public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
2432

25-
// Paper start - add "empty" choice
2633
/**
2734
* An "empty" recipe choice. Only valid as a recipe choice in
2835
* specific places. Check the javadocs of a method before using it
2936
* to be sure it's valid for that recipe and ingredient type.
3037
*
3138
* @return the empty recipe choice
3239
*/
33-
static @NotNull RecipeChoice empty() {
40+
static RecipeChoice empty() {
3441
return EmptyRecipeChoice.INSTANCE;
3542
}
36-
// Paper end
43+
44+
/**
45+
* Creates a new recipe choice based on a {@link RegistryKeySet} of item types.
46+
* Can either be created via {@link RegistryKeySet#keySet(RegistryKey, TypedKey[])}
47+
* or obtained from {@link org.bukkit.Registry#getTag(TagKey)}.
48+
*
49+
* @param itemTypes the item types to match
50+
* @return a new recipe choice
51+
*/
52+
@Contract(pure = true, value = "_ -> new")
53+
@ApiStatus.Experimental
54+
static ItemTypeChoice itemType(final RegistryKeySet<ItemType> itemTypes) {
55+
return new ItemTypeRecipeChoiceImpl(itemTypes);
56+
}
3757

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

48-
@NotNull
4967
RecipeChoice clone();
5068

5169
@Override
52-
boolean test(@NotNull ItemStack itemStack);
70+
boolean test(ItemStack itemStack);
5371

5472
// Paper start - check valid ingredients
5573
@org.jetbrains.annotations.ApiStatus.Internal
56-
default @NotNull RecipeChoice validate(final boolean allowEmptyRecipes) {
74+
default RecipeChoice validate(final boolean allowEmptyRecipes) {
5775
return this;
5876
}
5977
// Paper end - check valid ingredients
6078

6179
/**
6280
* Represents a choice of multiple matching Materials.
6381
*/
64-
public static class MaterialChoice implements RecipeChoice {
82+
final class MaterialChoice implements RecipeChoice {
6583

6684
private List<Material> choices;
6785

68-
public MaterialChoice(@NotNull Material choice) {
86+
public MaterialChoice(Material choice) {
6987
this(Arrays.asList(choice));
7088
}
7189

72-
public MaterialChoice(@NotNull Material... choices) {
90+
public MaterialChoice(Material... choices) {
7391
this(Arrays.asList(choices));
7492
}
7593

@@ -79,11 +97,11 @@ public MaterialChoice(@NotNull Material... choices) {
7997
*
8098
* @param choices the tag
8199
*/
82-
public MaterialChoice(@NotNull Tag<Material> choices) {
100+
public MaterialChoice(Tag<Material> choices) {
83101
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
84102
}
85103

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

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

105123
@Override
106-
public boolean test(@NotNull ItemStack t) {
124+
public boolean test(ItemStack t) {
107125
for (Material match : choices) {
108126
if (t.getType() == match) {
109127
return true;
@@ -113,7 +131,6 @@ public boolean test(@NotNull ItemStack t) {
113131
return false;
114132
}
115133

116-
@NotNull
117134
@Override
118135
public ItemStack getItemStack() {
119136
ItemStack stack = new ItemStack(choices.get(0));
@@ -126,12 +143,10 @@ public ItemStack getItemStack() {
126143
return stack;
127144
}
128145

129-
@NotNull
130146
public List<Material> getChoices() {
131147
return Collections.unmodifiableList(choices);
132148
}
133149

134-
@NotNull
135150
@Override
136151
public MaterialChoice clone() {
137152
try {
@@ -175,7 +190,7 @@ public String toString() {
175190

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

193208
private List<ItemStack> choices;
194209

195-
public ExactChoice(@NotNull ItemStack stack) {
210+
public ExactChoice(ItemStack stack) {
196211
this(Arrays.asList(stack));
197212
}
198213

199-
public ExactChoice(@NotNull ItemStack... stacks) {
214+
public ExactChoice(ItemStack... stacks) {
200215
this(Arrays.asList(stacks));
201216
}
202217

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

214-
@NotNull
215229
@Override
216230
public ItemStack getItemStack() {
217231
return choices.get(0).clone();
218232
}
219233

220-
@NotNull
221234
public List<ItemStack> getChoices() {
222235
return Collections.unmodifiableList(choices);
223236
}
224237

225-
@NotNull
226238
@Override
227239
public ExactChoice clone() {
228240
try {
@@ -240,7 +252,7 @@ public ExactChoice clone() {
240252
}
241253

242254
@Override
243-
public boolean test(@NotNull ItemStack t) {
255+
public boolean test(ItemStack t) {
244256
for (ItemStack match : choices) {
245257
if (t.isSimilar(match)) {
246258
return true;
@@ -282,12 +294,29 @@ public String toString() {
282294

283295
// Paper start - check valid ingredients
284296
@Override
285-
public @NotNull RecipeChoice validate(final boolean allowEmptyRecipes) {
297+
public RecipeChoice validate(final boolean allowEmptyRecipes) {
286298
if (this.choices.stream().anyMatch(s -> s.getType().isAir())) {
287299
throw new IllegalArgumentException("RecipeChoice.ExactChoice cannot contain air");
288300
}
289301
return this;
290302
}
291303
// Paper end - check valid ingredients
292304
}
305+
306+
/**
307+
* Represents a choice that will be valid if the {@link ItemStack#getType()}
308+
* matches any of the item types in the set.
309+
* @see #itemType(RegistryKeySet)
310+
*/
311+
@ApiStatus.Experimental
312+
@ApiStatus.NonExtendable
313+
interface ItemTypeChoice extends RecipeChoice {
314+
315+
/**
316+
* Gets the set of item types that this choice will match.
317+
*
318+
* @return the set of item types
319+
*/
320+
RegistryKeySet<ItemType> itemTypes();
321+
}
293322
}

paper-server/patches/features/0020-Improve-exact-choice-recipe-ingredients.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ index 6bbe2e51ef71d193e0a5d3cace2b0ad1760ce759..83ccde54c625d40dc595e000c533f60a
341341
}
342342

343343
diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java
344-
index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d521bd76ba 100644
344+
index ac4520afe510df9b1dd256d13dcb0768c83edb70..da95f40503f3696c8daa8fd0e947508fe43060ba 100644
345345
--- a/net/minecraft/world/item/crafting/Ingredient.java
346346
+++ b/net/minecraft/world/item/crafting/Ingredient.java
347347
@@ -21,7 +21,7 @@ import net.minecraft.world.item.Items;
@@ -354,7 +354,7 @@ index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d5
354354
.map(Ingredient::new, ingredient -> ingredient.values);
355355
public static final StreamCodec<RegistryFriendlyByteBuf, Optional<Ingredient>> OPTIONAL_CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
356356
@@ -35,20 +35,24 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
357-
private final HolderSet<Item> values;
357+
public final HolderSet<Item> values;
358358
// CraftBukkit start
359359
@javax.annotation.Nullable
360360
- private java.util.List<ItemStack> itemStacks;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--- a/net/minecraft/core/HolderSet.java
2+
+++ b/net/minecraft/core/HolderSet.java
3+
@@ -166,6 +_,8 @@
4+
private final TagKey<T> key;
5+
@Nullable
6+
private List<Holder<T>> contents;
7+
+ @Nullable
8+
+ private Set<io.papermc.paper.registry.TypedKey<?>> typedKeys; // Paper - cache typed key set for constant contains calls
9+
10+
Named(HolderOwner<T> owner, TagKey<T> key) {
11+
this.owner = owner;
12+
@@ -174,6 +_,7 @@
13+
14+
void bind(List<Holder<T>> contents) {
15+
this.contents = List.copyOf(contents);
16+
+ this.typedKeys = null; // Paper - reset if tag is re-bound
17+
}
18+
19+
public TagKey<T> key() {
20+
@@ -218,5 +_,15 @@
21+
public boolean canSerializeIn(HolderOwner<T> owner) {
22+
return this.owner.canSerializeIn(owner);
23+
}
24+
+
25+
+ // Paper start - cache typed key set for constant contains calls
26+
+ public boolean contains(io.papermc.paper.registry.TypedKey<?> key) {
27+
+ if (this.typedKeys == null) {
28+
+ this.typedKeys = this.contents().stream().map(h -> io.papermc.paper.registry.PaperRegistries.fromNms(h.unwrapKey().orElseThrow())).collect(java.util.stream.Collectors.toUnmodifiableSet());
29+
+ }
30+
+
31+
+ return this.typedKeys.contains(key);
32+
+ }
33+
+ // Paper end
34+
}
35+
}

paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
--- a/net/minecraft/world/item/crafting/Ingredient.java
22
+++ b/net/minecraft/world/item/crafting/Ingredient.java
3-
@@ -33,6 +_,25 @@
3+
@@ -32,7 +_,26 @@
4+
public static final Codec<HolderSet<Item>> NON_AIR_HOLDER_SET_CODEC = HolderSetCodec.create(Registries.ITEM, Item.CODEC, false);
45
public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC)
56
.xmap(Ingredient::new, ingredient -> ingredient.values);
6-
private final HolderSet<Item> values;
7+
- private final HolderSet<Item> values;
8+
+ public final HolderSet<Item> values;
79
+ // CraftBukkit start
810
+ @javax.annotation.Nullable
911
+ private java.util.List<ItemStack> itemStacks;

paper-server/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ public RegistryKey<T> registryKey() {
4646

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

5452
@Override

paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.bukkit.craftbukkit.inventory;
22

33
import com.google.common.base.Preconditions;
4+
import io.papermc.paper.registry.RegistryKey;
5+
import io.papermc.paper.registry.data.util.Conversions;
6+
import io.papermc.paper.registry.set.PaperRegistrySets;
7+
import io.papermc.paper.registry.set.RegistryKeySet;
48
import java.util.ArrayList;
59
import java.util.List;
610
import java.util.Optional;
@@ -9,6 +13,7 @@
913
import net.minecraft.world.item.crafting.Ingredient;
1014
import org.bukkit.NamespacedKey;
1115
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
16+
import org.bukkit.inventory.ItemType;
1217
import org.bukkit.inventory.Recipe;
1318
import org.bukkit.inventory.RecipeChoice;
1419
import org.bukkit.inventory.recipe.CookingBookCategory;
@@ -32,6 +37,8 @@ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) {
3237

3338
if (bukkit == null) {
3439
stack = Ingredient.of();
40+
} else if (bukkit instanceof final RecipeChoice.ItemTypeChoice itemTypeChoice) {
41+
stack = Ingredient.of(PaperRegistrySets.convertToNms(Registries.ITEM, Conversions.global().lookup(), itemTypeChoice.itemTypes()));
3542
} else if (bukkit instanceof RecipeChoice.MaterialChoice) {
3643
stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat)));
3744
} else if (bukkit instanceof RecipeChoice.ExactChoice) {
@@ -70,6 +77,9 @@ public static RecipeChoice toBukkit(Ingredient list) {
7077
}
7178

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

0 commit comments

Comments
 (0)