--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -5,19 +5,49 @@
 import net.minecraft.util.Mth;
 import net.minecraft.world.item.ItemStack;
 
+import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftMerchantRecipe; // CraftBukkit
+
 public class MerchantOffer {
+
     public ItemStack baseCostA;
     public ItemStack costB;
     public final ItemStack result;
     public int uses;
     public int maxUses;
-    public boolean rewardExp = true;
+    public boolean rewardExp;
     public int specialPriceDiff;
     public int demand;
     public float priceMultiplier;
-    public int xp = 1;
+    public int xp;
+    public boolean ignoreDiscounts; // Paper
+    // CraftBukkit start
+    private CraftMerchantRecipe bukkitHandle;
+
+    public CraftMerchantRecipe asBukkit() {
+        return (this.bukkitHandle == null) ? this.bukkitHandle = new CraftMerchantRecipe(this) : this.bukkitHandle;
+    }
+
+    public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, CraftMerchantRecipe bukkit) {
+        this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, 0, bukkit);
+    }
+
+    public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) {
+        // Paper start - add ignoreDiscounts param
+        this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, false, bukkit);
+    }
+    public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) {
+        this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, 0, ignoreDiscounts, bukkit);
+    }
+    public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) {
+        this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, ignoreDiscounts);
+        // Paper end
+        this.bukkitHandle = bukkit;
+    }
+    // CraftBukkit end
 
     public MerchantOffer(CompoundTag nbt) {
+        this.rewardExp = true;
+        this.xp = 1;
         this.baseCostA = ItemStack.of(nbt.getCompound("buy"));
         this.costB = ItemStack.of(nbt.getCompound("buyB"));
         this.result = ItemStack.of(nbt.getCompound("sell"));
@@ -42,6 +72,7 @@
 
         this.specialPriceDiff = nbt.getInt("specialPrice");
         this.demand = nbt.getInt("demand");
+        this.ignoreDiscounts = nbt.getBoolean("Paper.IgnoreDiscounts"); // Paper
     }
 
     public MerchantOffer(ItemStack buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) {
@@ -53,10 +84,21 @@
     }
 
     public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier) {
-        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0);
+        // Paper start - add ignoreDiscounts param
+        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, false);
+    }
+    public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, boolean ignoreDiscounts) {
+        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0, ignoreDiscounts);
     }
 
     public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) {
+        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, demandBonus, false);
+    }
+    public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus, boolean ignoreDiscounts) {
+        this.ignoreDiscounts = ignoreDiscounts;
+        // Paper end
+        this.rewardExp = true;
+        this.xp = 1;
         this.baseCostA = firstBuyItem;
         this.costB = secondBuyItem;
         this.result = sellItem;
@@ -68,6 +110,8 @@
     }
 
     private MerchantOffer(MerchantOffer offer) {
+        this.rewardExp = true;
+        this.xp = 1;
         this.baseCostA = offer.baseCostA.copy();
         this.costB = offer.costB.copy();
         this.result = offer.result.copy();
@@ -89,7 +133,9 @@
             return ItemStack.EMPTY;
         } else {
             int i = this.baseCostA.getCount();
-            int j = Math.max(0, Mth.floor((float)(i * this.demand) * this.priceMultiplier));
+            if (i <= 0) return ItemStack.EMPTY; // CraftBukkit - SPIGOT-5476
+            int j = Math.max(0, Mth.floor((float) (i * this.demand) * this.priceMultiplier));
+
             return this.baseCostA.copyWithCount(Mth.clamp(i + j + this.specialPriceDiff, 1, this.baseCostA.getItem().getMaxStackSize()));
         }
     }
@@ -103,7 +149,7 @@
     }
 
     public void updateDemand() {
-        this.demand = this.demand + this.uses - (this.maxUses - this.uses);
+        this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper
     }
 
     public ItemStack assemble() {
@@ -171,18 +217,20 @@
     }
 
     public CompoundTag createTag() {
-        CompoundTag compoundTag = new CompoundTag();
-        compoundTag.put("buy", this.baseCostA.save(new CompoundTag()));
-        compoundTag.put("sell", this.result.save(new CompoundTag()));
-        compoundTag.put("buyB", this.costB.save(new CompoundTag()));
-        compoundTag.putInt("uses", this.uses);
-        compoundTag.putInt("maxUses", this.maxUses);
-        compoundTag.putBoolean("rewardExp", this.rewardExp);
-        compoundTag.putInt("xp", this.xp);
-        compoundTag.putFloat("priceMultiplier", this.priceMultiplier);
-        compoundTag.putInt("specialPrice", this.specialPriceDiff);
-        compoundTag.putInt("demand", this.demand);
-        return compoundTag;
+        CompoundTag nbttagcompound = new CompoundTag();
+
+        nbttagcompound.put("buy", this.baseCostA.save(new CompoundTag()));
+        nbttagcompound.put("sell", this.result.save(new CompoundTag()));
+        nbttagcompound.put("buyB", this.costB.save(new CompoundTag()));
+        nbttagcompound.putInt("uses", this.uses);
+        nbttagcompound.putInt("maxUses", this.maxUses);
+        nbttagcompound.putBoolean("rewardExp", this.rewardExp);
+        nbttagcompound.putInt("xp", this.xp);
+        nbttagcompound.putFloat("priceMultiplier", this.priceMultiplier);
+        nbttagcompound.putInt("specialPrice", this.specialPriceDiff);
+        nbttagcompound.putInt("demand", this.demand);
+        nbttagcompound.putBoolean("Paper.IgnoreDiscounts", this.ignoreDiscounts); // Paper
+        return nbttagcompound;
     }
 
     public boolean satisfiedBy(ItemStack first, ItemStack second) {
@@ -193,12 +241,13 @@
         if (sample.isEmpty() && given.isEmpty()) {
             return true;
         } else {
-            ItemStack itemStack = given.copy();
-            if (itemStack.getItem().canBeDepleted()) {
-                itemStack.setDamageValue(itemStack.getDamageValue());
+            ItemStack itemstack2 = given.copy();
+
+            if (itemstack2.getItem().canBeDepleted()) {
+                itemstack2.setDamageValue(itemstack2.getDamageValue());
             }
 
-            return ItemStack.isSameItem(itemStack, sample) && (!sample.hasTag() || itemStack.hasTag() && NbtUtils.compareNbt(sample.getTag(), itemStack.getTag(), false));
+            return ItemStack.isSameItem(itemstack2, sample) && (!sample.hasTag() || itemstack2.hasTag() && NbtUtils.compareNbt(sample.getTag(), itemstack2.getTag(), false));
         }
     }
 
@@ -206,7 +255,11 @@
         if (!this.satisfiedBy(firstBuyStack, secondBuyStack)) {
             return false;
         } else {
-            firstBuyStack.shrink(this.getCostA().getCount());
+            // CraftBukkit start
+            if (!this.getCostA().isEmpty()) {
+                firstBuyStack.shrink(this.getCostA().getCount());
+            }
+            // CraftBukkit end
             if (!this.getCostB().isEmpty()) {
                 secondBuyStack.shrink(this.getCostB().getCount());
             }
