-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add Builder class for MerchantRecipe #14127
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,8 @@ | |
| import org.bukkit.entity.Villager; | ||
| import org.bukkit.potion.PotionEffectType; | ||
| import org.bukkit.util.NumberConversions; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
| import org.jspecify.annotations.NullMarked; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| /** | ||
| * Represents a merchant's trade. | ||
|
|
@@ -46,9 +46,10 @@ | |
| * constraining the resulting value between <code>1</code> and the item stack's | ||
| * {@link ItemStack#getMaxStackSize() maximum stack size}. | ||
| */ | ||
| @NullMarked | ||
| public class MerchantRecipe implements Recipe { | ||
|
|
||
| private ItemStack result; | ||
| private final ItemStack result; | ||
| private List<ItemStack> ingredients = new ArrayList<ItemStack>(); | ||
| private int uses; | ||
| private int maxUses; | ||
|
|
@@ -59,26 +60,26 @@ public class MerchantRecipe implements Recipe { | |
| private float priceMultiplier; | ||
| private boolean ignoreDiscounts; // Paper | ||
|
|
||
| public MerchantRecipe(@NotNull ItemStack result, int maxUses) { | ||
| public MerchantRecipe(ItemStack result, int maxUses) { | ||
| this(result, 0, maxUses, false); | ||
| } | ||
|
|
||
| public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward) { | ||
| public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward) { | ||
| this(result, uses, maxUses, experienceReward, 0, 0.0F, 0, 0); | ||
| } | ||
|
|
||
| public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier) { | ||
| public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier) { | ||
| this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, 0, 0); | ||
| } | ||
|
|
||
| public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice) { | ||
| public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice) { | ||
| // Paper start - add ignoreDiscounts param | ||
| this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, demand, specialPrice, false); | ||
| } | ||
| public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, boolean ignoreDiscounts) { | ||
| public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, boolean ignoreDiscounts) { | ||
| this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, 0, 0, ignoreDiscounts); | ||
| } | ||
| public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { | ||
| public MerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { | ||
| Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper | ||
| this.ignoreDiscounts = ignoreDiscounts; | ||
| // Paper end | ||
|
|
@@ -93,19 +94,18 @@ public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean | |
| } | ||
|
|
||
| // Paper start - add copy ctor | ||
| public MerchantRecipe(@NotNull MerchantRecipe recipe) { | ||
| public MerchantRecipe(MerchantRecipe recipe) { | ||
| this(recipe.result.clone(), recipe.uses, recipe.maxUses, recipe.experienceReward, recipe.villagerExperience, recipe.priceMultiplier, recipe.demand, recipe.specialPrice, recipe.ignoreDiscounts); | ||
| this.setIngredients(recipe.ingredients); | ||
| } | ||
| // Paper end | ||
|
|
||
| @NotNull | ||
| @Override | ||
| public ItemStack getResult() { | ||
| return result.clone(); // Paper | ||
| } | ||
|
|
||
| public void addIngredient(@NotNull ItemStack item) { | ||
| public void addIngredient(ItemStack item) { | ||
| Preconditions.checkState(ingredients.size() < 2, "MerchantRecipe can only have maximum 2 ingredients"); | ||
| Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); // Paper | ||
| ingredients.add(item.clone()); | ||
|
|
@@ -115,7 +115,7 @@ public void removeIngredient(int index) { | |
| ingredients.remove(index); | ||
| } | ||
|
|
||
| public void setIngredients(@NotNull List<ItemStack> ingredients) { | ||
| public void setIngredients(List<ItemStack> ingredients) { | ||
| Preconditions.checkState(ingredients.size() <= 2, "MerchantRecipe can only have maximum 2 ingredients"); | ||
| this.ingredients = new ArrayList<ItemStack>(); | ||
| for (ItemStack item : ingredients) { | ||
|
|
@@ -124,7 +124,6 @@ public void setIngredients(@NotNull List<ItemStack> ingredients) { | |
| } | ||
| } | ||
|
|
||
| @NotNull | ||
| public List<ItemStack> getIngredients() { | ||
| List<ItemStack> copy = new ArrayList<ItemStack>(); | ||
| for (ItemStack item : ingredients) { | ||
|
|
@@ -146,7 +145,7 @@ public ItemStack getAdjustedIngredient1() { | |
| return null; | ||
| } | ||
|
|
||
| ItemStack firstIngredient = this.ingredients.get(0).clone(); | ||
| ItemStack firstIngredient = this.ingredients.getFirst().clone(); | ||
| adjust(firstIngredient); | ||
| return firstIngredient; | ||
| } | ||
|
|
@@ -319,4 +318,281 @@ public void setIgnoreDiscounts(boolean ignoreDiscounts) { | |
| this.ignoreDiscounts = ignoreDiscounts; | ||
| } | ||
| // Paper end | ||
|
|
||
| /** | ||
| * Creates a {@link Builder} instance to use for modifying values such as the result | ||
| * item.<br> | ||
| * This Builder class will contain all the values from this MerchantRecipe instance. | ||
| * | ||
| * @return New Builder class with values from this MerchantRecipe applied. | ||
| */ | ||
| public Builder builder() { | ||
| return new Builder(this); | ||
| } | ||
|
|
||
| /** | ||
| * Builder class for creating a new {@link MerchantRecipe}. | ||
| * This class allows the result ItemStack to be modified until the | ||
| * {@link #build() the MerchantRecipe is build}. | ||
| * | ||
| * <p>A Builder copy of an existing MerchantRecipe can be obtained through | ||
| * its {@link MerchantRecipe#builder() builder method}. | ||
| */ | ||
| @NullMarked | ||
| public static class Builder { | ||
| private ItemStack result; | ||
| private List<ItemStack> ingredients = new ArrayList<>(); | ||
| private int uses = 0; | ||
| private int maxUses; | ||
| private boolean experienceReward = false; | ||
| private int specialPrice = 0; | ||
| private int demand = 0; | ||
| private int villagerExperience = 0; | ||
| private float priceMultiplier = 0.0F; | ||
| private boolean ignoreDiscounts = false; | ||
|
|
||
| /** | ||
| * Basic constructor to create a new Builder class from a provided result | ||
| * ItemStack and maxUses integer.<br> | ||
| * The created Builder will have all its other values be empty, zero or | ||
| * false, depending on the type. | ||
| * | ||
| * @param result The ItemStack to use as the result for the MerchantRecipe. | ||
| * @param maxUses Number of max uses for this MerchantRecipe | ||
| */ | ||
| public Builder(ItemStack result, int maxUses) { | ||
| Preconditions.checkArgument(!result.isEmpty(), "Result cannot be empty"); | ||
| this.result = result; | ||
| this.maxUses = maxUses; | ||
| } | ||
|
|
||
| /** | ||
| * Constructor for creating a Builder instance from an existing | ||
| * {@link MerchantRecipe} instance.<br> | ||
| * This Builder instance will have all its values set to what the provided | ||
| * MerchantRecipe had set. | ||
| * | ||
| * @param merchantRecipe The MerchantRecipe instance to create a Builder instance from. | ||
| */ | ||
| public Builder(MerchantRecipe merchantRecipe) { | ||
| this.result = merchantRecipe.getResult(); | ||
| this.ingredients = merchantRecipe.getIngredients(); | ||
| this.uses = merchantRecipe.getUses(); | ||
| this.maxUses = merchantRecipe.getMaxUses(); | ||
| this.experienceReward = merchantRecipe.hasExperienceReward(); | ||
| this.specialPrice = merchantRecipe.getSpecialPrice(); | ||
| this.demand = merchantRecipe.getDemand(); | ||
| this.villagerExperience = merchantRecipe.getVillagerExperience(); | ||
| this.priceMultiplier = merchantRecipe.getPriceMultiplier(); | ||
| this.ignoreDiscounts = merchantRecipe.shouldIgnoreDiscounts(); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the {@link ItemStack} to use as the result for this MerchantRecipe. | ||
| * | ||
| * @param result Result ItemStack for this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setResult(ItemStack result) { | ||
| Preconditions.checkArgument(!result.isEmpty(), "Result cannot be empty."); | ||
| this.result = result.clone(); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Adds an {@link ItemStack} as a new Ingredient for this MerchantRecipe.<br> | ||
| * The position of the ItemStack in the list determines which ingredient | ||
| * it is and there can't be more than 2 in total. | ||
| * | ||
| * @param item The ItemStack to add. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder addIngredient(ItemStack item) { | ||
| Preconditions.checkState(ingredients.size() < 2, "Recipe can only have maximum 2 ingredients"); | ||
| Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); | ||
| ingredients.add(item.clone()); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Removes an ingredient from the provided index in the ingredients list. | ||
| * | ||
| * @param index position in the list to remove the ItemStack from. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder removeIngredient(int index) { | ||
| ingredients.remove(index); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the {@link ItemStack List of ItemStacks} to use for this MerchantRecipe. | ||
| * | ||
| * @param ingredients List of ItemStacks to use as the ingredients. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setIngredients(List<ItemStack> ingredients) { | ||
| Preconditions.checkState(ingredients.size() <= 2, "Recipe can only have maximum 2 ingredients"); | ||
| List<ItemStack> copy = new ArrayList<>(); | ||
| for (ItemStack item : ingredients) { | ||
| Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); | ||
| copy.add(item); | ||
| } | ||
| this.ingredients = copy; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Resets the demand for this MerchantRecipe to 0.<br> | ||
| * This is a convenience method for {@code setDemand(0)}. | ||
| * | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder resetDemand() { | ||
| return setDemand(0); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the demand for this MerchantRecipe. | ||
| * @param demand Demant for this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setDemand(int demand) { | ||
| this.demand = demand; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Resets the special price for this MerchantRecipe to 0.<br> | ||
| * This is a convenience method for {@code setSpecialPrice(0)}. | ||
| * | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder resetSpecialPrice() { | ||
| return setSpecialPrice(0); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the special price for this MerchantRecipe. | ||
| * @param specialPrice Special Price for this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setSpecialPrice(int specialPrice) { | ||
| this.specialPrice = specialPrice; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Resets the Use count for this MerchantRecipe to 0.<br> | ||
| * This is a convenience method for {@code setUses(0)}. | ||
| * | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder resetUses() { | ||
| return setUses(0); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the uses for this MerchantRecipe. | ||
| * | ||
| * @param uses Use count for this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setUses(int uses) { | ||
| this.uses = uses; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the max uses for this MerchantRecipe. | ||
| * | ||
| * @param maxUses Max use count for this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setMaxUses(int maxUses) { | ||
| this.maxUses = maxUses; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Enables the MerchantRecipe to give experience to the player on | ||
| * completing the trade.<br> | ||
| * This is a convenience method for {@code setExperienceReward(true)}. | ||
| * | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder experienceReward() { | ||
| return setExperienceReward(true); | ||
| } | ||
|
|
||
| /** | ||
| * Sets whether this MerchantRecipe should give experience to a player | ||
| * on completing the trade. | ||
| * | ||
| * @param experienceReward Setting whether the MerchantRecipe should give experience. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setExperienceReward(boolean experienceReward) { | ||
| this.experienceReward = experienceReward; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets amount of experience the Villager gains when completing a trade. | ||
| * | ||
| * @param villagerExperience Amount of experience the villager should gain. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setVillageExperience(int villagerExperience) { | ||
| this.villagerExperience = villagerExperience; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the multiplier to apply on prices for this MerchantRecipe. | ||
| * | ||
| * @param priceMultiplier Multiplier to apply to this MerchantRecipe. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setPriceMultiplier(float priceMultiplier) { | ||
| this.priceMultiplier = priceMultiplier; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Enables this MerchantRecipe to ignore any discounts from cases like a | ||
| * Player having the {@link PotionEffectType#HERO_OF_THE_VILLAGE Hero of the Village} | ||
| * effect.<br> | ||
| * This is a convenience method for {@code setIgnoreDiscounts(true)}. | ||
| * | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder ignoreDiscounts() { | ||
| return setIgnoreDiscounts(true); | ||
| } | ||
|
|
||
| /** | ||
| * Sets whether this MerchantRecipe should ignore discounts applued from | ||
| * cases like a Player having the {@link PotionEffectType#HERO_OF_THE_VILLAGE Hero of the Village} | ||
| * effect. | ||
| * | ||
| * @param ignoreDiscounts Whether this MerchantRecipe should ignore discounts. | ||
| * @return This Builder for chaining purposes. | ||
| */ | ||
| public Builder setIgnoreDiscounts(boolean ignoreDiscounts) { | ||
| this.ignoreDiscounts = ignoreDiscounts; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new {@link MerchantRecipe} instance with the values of this class | ||
| * applies. | ||
| * | ||
| * @return New MerchantRecipe class. | ||
| */ | ||
| public MerchantRecipe build() { | ||
| return new MerchantRecipe(result, uses, maxUses, experienceReward, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is more of a genuine question rather than a review point. Are there "required" entries for a MerchantRecipe? Also would it do any good to enforce those here?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Result and max uses are required given the Constructor with fewest parameters for MerchantRecipe has those set. |
||
| villagerExperience, priceMultiplier, demand, specialPrice, ignoreDiscounts); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we are exposing these constructors in favor of static API methods? I actually think the normal builder method on MerchantRecipe that isn't static is also okay, but this feels like a loose end that could be tied up pretty easily.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can make the constructor private and expose a static builder method.
Would it be good to have a
builder(ItemStack, int)and abuilder(MerchantRecipe)or should we only allowbuilder(ItemStack, int)and have a builder from a MerchantRecipe through its builder method?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the latter would be best
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rename the builder method to #toBuilder to match the data component builder and probably obsolete a bunch of constructors on MerchantRecipe (the builder being preferred).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If by obsolete you mean mark them as deprecated, as I don't think a breaking change in the form of removing constructors should be considered here. Unless Paper team would be fine with such a change.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obselete is a jetbrains annotation namely ApiStatus.Obselete. I agree with Lulu here that's probably a smart addition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which constructors would be best to obsolete?
The ones with fewest, or most arguments?