--- a/net/minecraft/world/entity/npc/WanderingTrader.java
+++ b/net/minecraft/world/entity/npc/WanderingTrader.java
@@ -1,6 +1,7 @@
 package net.minecraft.world.entity.npc;
 
 import java.util.EnumSet;
+import java.util.Iterator;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.nbt.CompoundTag;
@@ -46,52 +47,52 @@
 import net.minecraft.world.phys.Vec3;
 import org.apache.commons.lang3.tuple.Pair;
 
-public class WanderingTrader extends AbstractVillager {
+// CraftBukkit start
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe;
+import org.bukkit.entity.AbstractVillager;
+import org.bukkit.event.entity.EntityRemoveEvent;
+import org.bukkit.event.entity.VillagerAcquireTradeEvent;
+// CraftBukkit end
+
+public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager {
+
     private static final int NUMBER_OF_TRADE_OFFERS = 5;
     @Nullable
     private BlockPos wanderTarget;
     private int despawnDelay;
+    // Paper start - Add more WanderingTrader API
+    public boolean canDrinkPotion = true;
+    public boolean canDrinkMilk = true;
+    // Paper end - Add more WanderingTrader API
 
     public WanderingTrader(EntityType<? extends WanderingTrader> type, Level world) {
         super(type, world);
+        //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set.
     }
 
     @Override
     protected void registerGoals() {
         this.goalSelector.addGoal(0, new FloatGoal(this));
-        this.goalSelector
-            .addGoal(
-                0,
-                new UseItemGoal<>(
-                    this,
-                    PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY),
-                    SoundEvents.WANDERING_TRADER_DISAPPEARED,
-                    wanderingTrader -> this.level().isNight() && !wanderingTrader.isInvisible()
-                )
-            );
-        this.goalSelector
-            .addGoal(
-                0,
-                new UseItemGoal<>(
-                    this,
-                    new ItemStack(Items.MILK_BUCKET),
-                    SoundEvents.WANDERING_TRADER_REAPPEARED,
-                    wanderingTrader -> this.level().isDay() && wanderingTrader.isInvisible()
-                )
-            );
+        this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> {
+            return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
+        }));
+        this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> {
+            return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
+        }));
         this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Evoker.class, 12.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Vindicator.class, 8.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Vex.class, 8.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Pillager.class, 15.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Illusioner.class, 12.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zoglin.class, 10.0F, 0.5, 0.5));
-        this.goalSelector.addGoal(1, new PanicGoal(this, 0.5));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Evoker.class, 12.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Vindicator.class, 8.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Vex.class, 8.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Pillager.class, 15.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Illusioner.class, 12.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zoglin.class, 10.0F, 0.5D, 0.5D));
+        this.goalSelector.addGoal(1, new PanicGoal(this, 0.5D));
         this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this));
-        this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0, 0.35));
-        this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35));
-        this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35));
+        this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0D, 0.35D));
+        this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35D));
+        this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35D));
         this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F));
         this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
     }
@@ -109,8 +110,9 @@
 
     @Override
     public InteractionResult mobInteract(Player player, InteractionHand hand) {
-        ItemStack itemStack = player.getItemInHand(hand);
-        if (!itemStack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isBaby()) {
+        ItemStack itemstack = player.getItemInHand(hand);
+
+        if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isBaby()) {
             if (hand == InteractionHand.MAIN_HAND) {
                 player.awardStat(Stats.TALKED_TO_VILLAGER);
             }
@@ -135,28 +137,45 @@
         if (this.level().enabledFeatures().contains(FeatureFlags.TRADE_REBALANCE)) {
             this.experimentalUpdateTrades();
         } else {
-            VillagerTrades.ItemListing[] itemListings = VillagerTrades.WANDERING_TRADER_TRADES.get(1);
-            VillagerTrades.ItemListing[] itemListings2 = VillagerTrades.WANDERING_TRADER_TRADES.get(2);
-            if (itemListings != null && itemListings2 != null) {
-                MerchantOffers merchantOffers = this.getOffers();
-                this.addOffersFromItemListings(merchantOffers, itemListings, 5);
-                int i = this.random.nextInt(itemListings2.length);
-                VillagerTrades.ItemListing itemListing = itemListings2[i];
-                MerchantOffer merchantOffer = itemListing.getOffer(this, this.random);
-                if (merchantOffer != null) {
-                    merchantOffers.add(merchantOffer);
+            VillagerTrades.ItemListing[] avillagertrades_imerchantrecipeoption = (VillagerTrades.ItemListing[]) VillagerTrades.WANDERING_TRADER_TRADES.get(1);
+            VillagerTrades.ItemListing[] avillagertrades_imerchantrecipeoption1 = (VillagerTrades.ItemListing[]) VillagerTrades.WANDERING_TRADER_TRADES.get(2);
+
+            if (avillagertrades_imerchantrecipeoption != null && avillagertrades_imerchantrecipeoption1 != null) {
+                MerchantOffers merchantrecipelist = this.getOffers();
+
+                this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 5);
+                int i = this.random.nextInt(avillagertrades_imerchantrecipeoption1.length);
+                VillagerTrades.ItemListing villagertrades_imerchantrecipeoption = avillagertrades_imerchantrecipeoption1[i];
+                MerchantOffer merchantrecipe = villagertrades_imerchantrecipeoption.getOffer(this, this.random);
+
+                if (merchantrecipe != null) {
+                    // CraftBukkit start
+                    VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit());
+                    // Suppress during worldgen
+                    if (this.valid) {
+                        Bukkit.getPluginManager().callEvent(event);
+                    }
+                    if (!event.isCancelled()) {
+                        merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft());
+                    }
+                    // CraftBukkit end
                 }
+
             }
         }
     }
 
     private void experimentalUpdateTrades() {
-        MerchantOffers merchantOffers = this.getOffers();
+        MerchantOffers merchantrecipelist = this.getOffers();
+        Iterator iterator = VillagerTrades.EXPERIMENTAL_WANDERING_TRADER_TRADES.iterator();
 
-        for (Pair<VillagerTrades.ItemListing[], Integer> pair : VillagerTrades.EXPERIMENTAL_WANDERING_TRADER_TRADES) {
-            VillagerTrades.ItemListing[] itemListings = pair.getLeft();
-            this.addOffersFromItemListings(merchantOffers, itemListings, pair.getRight());
+        while (iterator.hasNext()) {
+            Pair<VillagerTrades.ItemListing[], Integer> pair = (Pair) iterator.next();
+            VillagerTrades.ItemListing[] avillagertrades_imerchantrecipeoption = (VillagerTrades.ItemListing[]) pair.getLeft();
+
+            this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, (Integer) pair.getRight());
         }
+
     }
 
     @Override
@@ -166,6 +185,7 @@
         if (this.wanderTarget != null) {
             nbt.put("wander_target", NbtUtils.writeBlockPos(this.wanderTarget));
         }
+
     }
 
     @Override
@@ -175,7 +195,9 @@
             this.despawnDelay = nbt.getInt("DespawnDelay");
         }
 
-        NbtUtils.readBlockPos(nbt, "wander_target").ifPresent(wanderTarget -> this.wanderTarget = wanderTarget);
+        NbtUtils.readBlockPos(nbt, "wander_target").ifPresent((blockposition) -> {
+            this.wanderTarget = blockposition;
+        });
         this.setAge(Math.max(0, this.getAge()));
     }
 
@@ -188,8 +210,10 @@
     protected void rewardTradeXp(MerchantOffer offer) {
         if (offer.shouldRewardExp()) {
             int i = 3 + this.random.nextInt(4);
-            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i));
+
+            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
         }
+
     }
 
     @Override
@@ -236,64 +260,66 @@
         if (!this.level().isClientSide) {
             this.maybeDespawn();
         }
+
     }
 
     private void maybeDespawn() {
         if (this.despawnDelay > 0 && !this.isTrading() && --this.despawnDelay == 0) {
-            this.discard();
+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
         }
+
     }
 
     public void setWanderTarget(@Nullable BlockPos wanderTarget) {
         this.wanderTarget = wanderTarget;
     }
 
-    @Nullable
-    public BlockPos getWanderTarget() {
+    public @Nullable
+    BlockPos getWanderTarget() {
         return this.wanderTarget;
     }
 
-    class WanderToPositionGoal extends Goal {
+    private class WanderToPositionGoal extends Goal {
+
         final WanderingTrader trader;
         final double stopDistance;
         final double speedModifier;
 
-        WanderToPositionGoal(final WanderingTrader trader, final double proximityDistance, final double speed) {
-            this.trader = trader;
-            this.stopDistance = proximityDistance;
-            this.speedModifier = speed;
+        WanderToPositionGoal(final WanderingTrader entityvillagertrader, final double d0, final double d1) {
+            this.trader = entityvillagertrader;
+            this.stopDistance = d0;
+            this.speedModifier = d1;
             this.setFlags(EnumSet.of(Goal.Flag.MOVE));
         }
 
         @Override
         public void stop() {
-            this.trader.setWanderTarget(null);
+            this.trader.setWanderTarget((BlockPos) null);
             WanderingTrader.this.navigation.stop();
         }
 
         @Override
         public boolean canUse() {
-            BlockPos blockPos = this.trader.getWanderTarget();
-            return blockPos != null && this.isTooFarAway(blockPos, this.stopDistance);
+            BlockPos blockposition = this.trader.getWanderTarget();
+
+            return blockposition != null && this.isTooFarAway(blockposition, this.stopDistance);
         }
 
         @Override
         public void tick() {
-            BlockPos blockPos = this.trader.getWanderTarget();
-            if (blockPos != null && WanderingTrader.this.navigation.isDone()) {
-                if (this.isTooFarAway(blockPos, 10.0)) {
-                    Vec3 vec3 = new Vec3(
-                            (double)blockPos.getX() - this.trader.getX(),
-                            (double)blockPos.getY() - this.trader.getY(),
-                            (double)blockPos.getZ() - this.trader.getZ()
-                        )
-                        .normalize();
-                    Vec3 vec32 = vec3.scale(10.0).add(this.trader.getX(), this.trader.getY(), this.trader.getZ());
-                    WanderingTrader.this.navigation.moveTo(vec32.x, vec32.y, vec32.z, this.speedModifier);
+            BlockPos blockposition = this.trader.getWanderTarget();
+
+            if (blockposition != null && WanderingTrader.this.navigation.isDone()) {
+                if (this.isTooFarAway(blockposition, 10.0D)) {
+                    Vec3 vec3d = (new Vec3((double) blockposition.getX() - this.trader.getX(), (double) blockposition.getY() - this.trader.getY(), (double) blockposition.getZ() - this.trader.getZ())).normalize();
+                    Vec3 vec3d1 = vec3d.scale(10.0D).add(this.trader.getX(), this.trader.getY(), this.trader.getZ());
+
+                    WanderingTrader.this.navigation.moveTo(vec3d1.x, vec3d1.y, vec3d1.z, this.speedModifier);
                 } else {
-                    WanderingTrader.this.navigation.moveTo((double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ(), this.speedModifier);
+                    WanderingTrader.this.navigation.moveTo((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), this.speedModifier);
                 }
             }
+
         }
 
         private boolean isTooFarAway(BlockPos pos, double proximityDistance) {
