--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -21,6 +_,7 @@
                 Codec.INT.lenientOptionalFieldOf("demand", Integer.valueOf(0)).forGetter(merchantOffer -> merchantOffer.demand),
                 Codec.FLOAT.lenientOptionalFieldOf("priceMultiplier", Float.valueOf(0.0F)).forGetter(merchantOffer -> merchantOffer.priceMultiplier),
                 Codec.INT.lenientOptionalFieldOf("xp", Integer.valueOf(1)).forGetter(merchantOffer -> merchantOffer.xp)
+                , Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter(merchantOffer -> merchantOffer.ignoreDiscounts) // Paper
             )
             .apply(instance, MerchantOffer::new)
     );
@@ -37,6 +_,21 @@
     public int demand;
     public float priceMultiplier;
     public int xp;
+    public boolean ignoreDiscounts; // Paper - Add ignore discounts API
+
+    // CraftBukkit start
+    private org.bukkit.craftbukkit.inventory.@org.jspecify.annotations.Nullable CraftMerchantRecipe bukkitHandle;
+
+    public org.bukkit.craftbukkit.inventory.CraftMerchantRecipe asBukkit() {
+        return (this.bukkitHandle == null) ? this.bukkitHandle = new org.bukkit.craftbukkit.inventory.CraftMerchantRecipe(this) : this.bukkitHandle;
+    }
+
+    public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, org.bukkit.craftbukkit.inventory.CraftMerchantRecipe bukkit) { // Paper
+        this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand);
+        this.ignoreDiscounts = ignoreDiscounts; // Paper
+        this.bukkitHandle = bukkit;
+    }
+    // CraftBukkit end
 
     private MerchantOffer(
         ItemCost baseCostA,
@@ -49,6 +_,7 @@
         int demand,
         float priceMultiplier,
         int xp
+        , final boolean ignoreDiscounts // Paper
     ) {
         this.baseCostA = baseCostA;
         this.costB = costB;
@@ -60,6 +_,7 @@
         this.demand = demand;
         this.priceMultiplier = priceMultiplier;
         this.xp = xp;
+        this.ignoreDiscounts = ignoreDiscounts; // Paper
     }
 
     public MerchantOffer(ItemCost baseCostA, ItemStack result, int maxUses, int xp, float priceMultiplier) {
@@ -75,7 +_,7 @@
     }
 
     public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int _uses, int maxUses, int xp, float priceMultiplier, int demand) {
-        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp);
+        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp, false); // Paper
     }
 
     private MerchantOffer(MerchantOffer other) {
@@ -90,6 +_,7 @@
             other.demand,
             other.priceMultiplier,
             other.xp
+            , other.ignoreDiscounts // Paper
         );
     }
 
@@ -124,7 +_,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 - Fix MC-163962
     }
 
     public ItemStack assemble() {
@@ -205,7 +_,11 @@
         if (!this.satisfiedBy(playerOfferA, playerOfferB)) {
             return false;
         } else {
-            playerOfferA.shrink(this.getCostA().getCount());
+            // CraftBukkit start
+            if (!this.getCostA().isEmpty()) {
+                playerOfferA.shrink(this.getCostA().getCount());
+            }
+            // CraftBukkit end
             if (!this.getCostB().isEmpty()) {
                 playerOfferB.shrink(this.getCostB().getCount());
             }
