diff --git a/paper-api/src/main/java/org/bukkit/inventory/MerchantRecipe.java b/paper-api/src/main/java/org/bukkit/inventory/MerchantRecipe.java index 1fb4a1c537..620a4df816 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/MerchantRecipe.java +++ b/paper-api/src/main/java/org/bukkit/inventory/MerchantRecipe.java @@ -3,7 +3,12 @@ package org.bukkit.inventory; import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.List; +import org.bukkit.Material; +import org.bukkit.event.entity.VillagerReplenishTradeEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.NumberConversions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Represents a merchant's trade. @@ -16,6 +21,30 @@ import org.jetbrains.annotations.NotNull; * uses to increase. *
* A trade may or may not reward experience for being completed. + *
+ * During trades, the {@link MerchantRecipe} dynamically adjusts the amount of + * its first ingredient based on the following criteria: + * + * The adjusted amount of the first ingredient is calculated by adding up the + * original amount of the first ingredient, the demand scaled by the recipe's + * {@link #getPriceMultiplier price multiplier} and truncated to the next lowest + * integer value greater than or equal to 0, and the special price, and then + * constraining the resulting value between 1 and the item stack's + * {@link ItemStack#getMaxStackSize() maximum stack size}. * * @see org.bukkit.event.entity.VillagerReplenishTradeEvent */ @@ -26,6 +55,8 @@ public class MerchantRecipe implements Recipe { private int uses; private int maxUses; private boolean experienceReward; + private int specialPrice; + private int demand; private int villagerExperience; private float priceMultiplier; @@ -34,16 +65,22 @@ public class MerchantRecipe implements Recipe { } public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward) { - this(result, uses, maxUses, experienceReward, 0, 0.0F); + 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) { + 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) { this.result = result; this.uses = uses; this.maxUses = maxUses; this.experienceReward = experienceReward; this.villagerExperience = villagerExperience; this.priceMultiplier = priceMultiplier; + this.demand = demand; + this.specialPrice = specialPrice; } @NotNull @@ -78,6 +115,95 @@ public class MerchantRecipe implements Recipe { return copy; } + /** + * Gets the {@link #adjust(ItemStack) adjusted} first ingredient. + * + * @return the adjusted first ingredient, or null if this + * recipe has no ingredients + * @see #adjust(ItemStack) + */ + @Nullable + public ItemStack getAdjustedIngredient1() { + if (this.ingredients.isEmpty()) { + return null; + } + + ItemStack firstIngredient = this.ingredients.get(0).clone(); + adjust(firstIngredient); + return firstIngredient; + } + + /** + * Modifies the amount of the given {@link ItemStack} in the same way as + * MerchantRecipe dynamically adjusts the amount of the first ingredient + * during trading. + *
+ * This is calculated by adding up the original amount of the item, the + * demand scaled by the recipe's + * {@link #getPriceMultiplier price multiplier} and truncated to the next + * lowest integer value greater than or equal to 0, and the special price, + * and then constraining the resulting value between 1 and the + * {@link ItemStack}'s {@link ItemStack#getMaxStackSize() + * maximum stack size}. + * + * @param itemStack the item to adjust + */ + public void adjust(@Nullable ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() <= 0) { + return; + } + + int amount = itemStack.getAmount(); + int demandAdjustment = Math.max(0, NumberConversions.floor((float) (amount * getDemand()) * getPriceMultiplier())); + itemStack.setAmount(Math.max(1, Math.min(itemStack.getMaxStackSize(), amount + demandAdjustment + getSpecialPrice()))); + } + + /** + * Get the value of the demand for the item in {@link #getResult()}. + * + * @return the demand for the item + */ + public int getDemand() { + return demand; + } + + /** + * Set the value of the demand for the item in {@link #getResult()}. + *
+ * Note: This value is updated when the item is purchase + * + * @param demand demand value + */ + public void setDemand(int demand) { + this.demand = demand; + } + + /** + * Get the special price for this trade. + *
+ * Note: This value can be updated by + * {@link VillagerReplenishTradeEvent#getBonus()} or by + * {@link PotionEffectType#HERO_OF_THE_VILLAGE} + * + * @return special price value + */ + public int getSpecialPrice() { + return specialPrice; + } + + /** + * Set the special value for this trade. + *
+ * Note: This value can be updated by + * {@link VillagerReplenishTradeEvent#getBonus()} or by + * {@link PotionEffectType#HERO_OF_THE_VILLAGE} + * + * @param specialPrice special price value + */ + public void setSpecialPrice(int specialPrice) { + this.specialPrice = specialPrice; + } + /** * Get the number of times this trade has been used. *