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 #9996

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

derverdox
Copy link

@derverdox derverdox commented Dec 3, 2023

3 days ago I posted a pull request #9991 about adding CustomModelChoice. However, we came to the conclusion that we need a more generalized approach.
As a result, I began working on what is called PredicateChoice.
To make it work the StackedContents logic had to be edited.
In the ExtraContentsMap we don't store the exact ingredient items of every recipe ingredient. Instead, whenever we account a new stack in StackedContents logic we compare it to the provided predicates and store them as "ingredient items" if they fulfill at least one predicate.

We act like the items in the inventory of the player are the exact choices when any ingredient predicate returns true.

It gets more apparent when looking at the changes.
The first tests showed that it works like a charm.

Pros

  • With predicate choice one can implement his own version of ItemPredicate. This allows for the programmer to make the recipe check for whatever he wants.
  • They still work as intended if you want to use MaterialChoice or ExactChoice.

Cons

  • If you use PredicateChoice the predicate tests Bukkit ItemStacks. Consequently, NMS stacks need to instantiate their Craftbukkit mirror to make the checks happen.

Let me know your thoughts! :) Maybe I have missed something.

Copy link
Member

@Machine-Maker Machine-Maker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing all sorts of // Paper comments, but that's not a big deal.

Overall, this works with my initial testing. I haven't yet done any spark profiles yet to see if the performance is significantly worse or anything.

My main concern with this is the unnecessary replacement of MaterialChoice and ExactChoice. We should just be able to add a 3rd (PredicateChoice) to that lineup without needing to replace the others.

@Machine-Maker
Copy link
Member

This is just a quick mockup of what I had in mind. Ignore the API, it is just quicker to make it not an interface+record. I didn't exhaustively test it, but the test in the test-plugin work so far.

@derverdox
Copy link
Author

derverdox commented Dec 3, 2023

This is just a quick mockup of what I had in mind. Ignore the API, it is just quicker to make it not an interface+record. I didn't exhaustively test it, but the test in the test plugin work so far.

Looks good to me! :) My PR was more of a proof of concept. I am fine with your implementation.
Do you want me to fix my PR? Or do we just pick your's instead? I think it doesn't make a difference right?

@Machine-Maker
Copy link
Member

You can go ahead and fixup yours

@derverdox
Copy link
Author

You can go ahead and fixup yours

Allright

Copy link
Author

@derverdox derverdox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created two new patches. Your implementation heavily inspires them. It is way cleaner to use the logic when a predicate is used.
Even fewer code changes than before. I tested things myself, and it works like a charm :)

Since we know that the extra map logic works, we use working code and add more item contents to its storage. In this case, the items that match any predicate choice.

It is a simple solution that brings many new possibilities to the table.
Hope there is no fatal flaw in this idea.

Comment on lines 102 to 105
+ boolean isStackTypeRegistered = this.exactChoiceIds.getInt(stack) > -1;
+ final int id = this.registerExact(stack);
+ // We only want to add the stacking id to the list one time
+ if(!isStackTypeRegistered)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should just be able to check the id returned by registerExact if it is -1, it's already been added to the exactChoice map.

+ return Objects.equals(itemPredicate, that.itemPredicate);
+ }
+
+ public static interface ItemPredicate extends Predicate<ItemStack> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type shouldn't exist

+ /**
+ * Represents a choice that matches when the item predicate is fulfilled.
+ */
+ public static class PredicateChoice implements RecipeChoice {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an interface that is backed by a package-private record implementation with static methods to create instances of it.


public static final Ingredient EMPTY = new Ingredient(Stream.empty());
private final Ingredient.Value[] values;
+ @Nullable private RecipeChoice.PredicateChoice.ItemPredicate itemPredicate; // Paper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just have a public field for Predicate

Comment on lines 136 to 142
+ // Paper start - Adding PredicateChoice
+ public Ingredient(RecipeChoice.PredicateChoice.ItemPredicate itemPredicate) {
+ List<org.bukkit.inventory.ItemStack> bukkitChoices = itemPredicate.recipeBookExamples();
+ this.itemPredicate = itemPredicate;
+ this.values = bukkitChoices.stream().map(CraftItemStack::asNMSCopy).map(ItemValue::new).toArray(Value[]::new);
+ }
+ // Paper end - Adding PredicateChoice
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a custom constructor, do all the logic in CraftRecipe

Comment on lines 162 to 167
+ // Paper start - Adding PredicateChoice
+ @Nullable
+ public RecipeChoice.PredicateChoice.ItemPredicate getItemPredicate() {
+ return itemPredicate;
+ }
+ // Paper end - Adding PredicateChoice
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with a public field, no need for a getter

+ }
+ // Paper start - Adding PredicateChoice
+ else if(bukkit instanceof RecipeChoice.PredicateChoice predicateChoice){
+ stack = new Ingredient(predicateChoice.getItemPredicate());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use the regular constructor and pass in the recipe book examples as itemstacks and then assign the predicate field on the next line.

joshuaprince added a commit to joshuaprince/Purpur that referenced this pull request Dec 13, 2023
PaperMC/Paper#9996

I merged this change, added @NotNull tags to get the API to build, and deleted the existing Purpur item predicate API since it was conflicting.
@partydev
Copy link

partydev commented Jan 9, 2024

Upon testing this found that clicking the recipe book item does not move the items in your inventory to the crafting grid.
image
Ingredients have custom model data, lore, and attributes set. The recipe does work, just not the recipe books behavior. This may be more to blame on the recipe book patch than this patch.

@JakeGBLP
Copy link
Contributor

JakeGBLP commented Mar 7, 2024

Is this still being worked on?

@derverdox
Copy link
Author

Is this still being worked on?

Hello! I was working on my bachelor thesis lately so i was not able to fully work on this.
Will continue soon!

@derverdox
Copy link
Author

Upon testing this found that clicking the recipe book item does not move the items in your inventory to the crafting grid. image Ingredients have custom model data, lore, and attributes set. The recipe does work, just not the recipe books behavior. This may be more to blame on the recipe book patch than this patch.

Hey. I just uploaded a fix to the recipe book problem. The issue seems to be resolved. It would be nice if you could test as well! :)

@derverdox derverdox requested a review from Machine-Maker March 21, 2024 17:16
Copy link
Member

@Machine-Maker Machine-Maker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the diff in the server patch can be moved back to the patch that created it, such as simple renames, like the hasExactIngredients but that doesn't have to be done now, can be done before merging.

We also need to make it clear that mutating the stack within the Predicate provided in the create method does not support mutating the stack in an way. I don't want to open that can of worms, and I also don't want to copy the itemstack each time that predicate might be called.

+
+ @Override
+ public @NotNull RecipeChoice clone() {
+ return new PredicateChoiceImpl(predicate, recipeBookExamples());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has to clone each itemstack in the recipe book examples because ItemStack is a mutable type.

Comment on lines +65 to +79
+ static PredicateChoice create(@NotNull Predicate<ItemStack> predicate, ItemStack... recipeBookExamples){
+ Objects.requireNonNull(predicate, "The item predicate cannot be null!");
+ Objects.requireNonNull(predicate, "The mustHaveRecipeBookExample cannot be null!");
+ if(recipeBookExamples.length == 0)
+ throw new IllegalArgumentException("Please provide at least one recipe book example item!");
+ return new PredicateChoiceImpl(predicate, List.of(recipeBookExamples));
+ }
+
+ static PredicateChoice create(@NotNull Predicate<ItemStack> predicate, java.util.Collection<ItemStack> recipeBookExamples){
+ Objects.requireNonNull(predicate, "The item predicate cannot be null!");
+ Objects.requireNonNull(predicate, "The mustHaveRecipeBookExample cannot be null!");
+ if(recipeBookExamples.isEmpty())
+ throw new IllegalArgumentException("Please provide at least one recipe book example item!");
+ return new PredicateChoiceImpl(predicate, List.copyOf(recipeBookExamples));
+ }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move these methods up 1 level to be on RecipeChoice itself and named createPredicateChoice

An alternative would be to just make PredicateChoice a top-level class next to RecipeChoice and have create methods in it. Not a fan of the static factory methods inside an inner class.

+ interface PredicateChoice extends RecipeChoice {
+ static PredicateChoice create(@NotNull Predicate<ItemStack> predicate, ItemStack... recipeBookExamples){
+ Objects.requireNonNull(predicate, "The item predicate cannot be null!");
+ Objects.requireNonNull(predicate, "The mustHaveRecipeBookExample cannot be null!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line checks the exactly same thing as the one above... I think here should be recipeBookExamples?

+
+ static PredicateChoice create(@NotNull Predicate<ItemStack> predicate, java.util.Collection<ItemStack> recipeBookExamples){
+ Objects.requireNonNull(predicate, "The item predicate cannot be null!");
+ Objects.requireNonNull(predicate, "The mustHaveRecipeBookExample cannot be null!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line checks the exactly same thing as the one above... I think here should be recipeBookExamples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Changes required
Development

Successfully merging this pull request may close these issues.

6 participants