--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
@@ -1,9 +1,11 @@
 package net.minecraft.world.entity.npc;
 
+import java.util.Iterator;
 import java.util.Optional;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.tags.BiomeTags;
 import net.minecraft.util.Mth;
 import net.minecraft.util.RandomSource;
@@ -14,7 +16,6 @@
 import net.minecraft.world.entity.ai.village.poi.PoiManager;
 import net.minecraft.world.entity.ai.village.poi.PoiTypes;
 import net.minecraft.world.entity.animal.horse.TraderLlama;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.level.BlockGetter;
 import net.minecraft.world.level.CustomSpawner;
 import net.minecraft.world.level.GameRules;
@@ -23,6 +24,7 @@
 import net.minecraft.world.level.storage.ServerLevelData;
 
 public class WanderingTraderSpawner implements CustomSpawner {
+
     private static final int DEFAULT_TICK_DELAY = 1200;
     public static final int DEFAULT_SPAWN_DELAY = 24000;
     private static final int MIN_SPAWN_CHANCE = 25;
@@ -38,41 +40,53 @@
 
     public WanderingTraderSpawner(ServerLevelData properties) {
         this.serverLevelData = properties;
-        this.tickDelay = 1200;
-        this.spawnDelay = properties.getWanderingTraderSpawnDelay();
-        this.spawnChance = properties.getWanderingTraderSpawnChance();
-        if (this.spawnDelay == 0 && this.spawnChance == 0) {
-            this.spawnDelay = 24000;
-            properties.setWanderingTraderSpawnDelay(this.spawnDelay);
-            this.spawnChance = 25;
-            properties.setWanderingTraderSpawnChance(this.spawnChance);
-        }
+        // Paper start - Add Wandering Trader spawn rate config options
+        this.tickDelay = Integer.MIN_VALUE;
+        //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value
+        //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value
+        //if (this.spawnDelay == 0 && this.spawnChance == 0) {
+        //    this.spawnDelay = 24000;
+        //    properties.setWanderingTraderSpawnDelay(this.spawnDelay);
+        //    this.spawnChance = 25;
+        //    properties.setWanderingTraderSpawnChance(this.spawnChance);
+        //}
+        // Paper end - Add Wandering Trader spawn rate config options
+
     }
 
     @Override
     public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) {
+        // Paper start - Add Wandering Trader spawn rate config options
+        if (this.tickDelay == Integer.MIN_VALUE) {
+            this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
+            this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
+            this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
+        }
         if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) {
             return 0;
-        } else if (--this.tickDelay > 0) {
+        } else if (this.tickDelay - 1 > 0) {
+            this.tickDelay = this.tickDelay - 1;
             return 0;
         } else {
-            this.tickDelay = 1200;
-            this.spawnDelay -= 1200;
-            this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
+            this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
+            this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
+            //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
             if (this.spawnDelay > 0) {
                 return 0;
             } else {
-                this.spawnDelay = 24000;
+                this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
                 if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
                     return 0;
                 } else {
                     int i = this.spawnChance;
-                    this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75);
-                    this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
+
+                    // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
+                    this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax);
                     if (this.random.nextInt(100) > i) {
                         return 0;
                     } else if (this.spawn(world)) {
-                        this.spawnChance = 25;
+                        this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
+                        // Paper end - Add Wandering Trader spawn rate config options
                         return 1;
                     } else {
                         return 0;
@@ -83,33 +97,40 @@
     }
 
     private boolean spawn(ServerLevel world) {
-        Player player = world.getRandomPlayer();
-        if (player == null) {
+        ServerPlayer entityplayer = world.getRandomPlayer();
+
+        if (entityplayer == null) {
             return true;
         } else if (this.random.nextInt(10) != 0) {
             return false;
         } else {
-            BlockPos blockPos = player.blockPosition();
-            int i = 48;
-            PoiManager poiManager = world.getPoiManager();
-            Optional<BlockPos> optional = poiManager.find(poiType -> poiType.is(PoiTypes.MEETING), pos -> true, blockPos, 48, PoiManager.Occupancy.ANY);
-            BlockPos blockPos2 = optional.orElse(blockPos);
-            BlockPos blockPos3 = this.findSpawnPositionNear(world, blockPos2, 48);
-            if (blockPos3 != null && this.hasEnoughSpace(world, blockPos3)) {
-                if (world.getBiome(blockPos3).is(BiomeTags.WITHOUT_WANDERING_TRADER_SPAWNS)) {
+            BlockPos blockposition = entityplayer.blockPosition();
+            boolean flag = true;
+            PoiManager villageplace = world.getPoiManager();
+            Optional<BlockPos> optional = villageplace.find((holder) -> {
+                return holder.is(PoiTypes.MEETING);
+            }, (blockposition1) -> {
+                return true;
+            }, blockposition, 48, PoiManager.Occupancy.ANY);
+            BlockPos blockposition1 = (BlockPos) optional.orElse(blockposition);
+            BlockPos blockposition2 = this.findSpawnPositionNear(world, blockposition1, 48);
+
+            if (blockposition2 != null && this.hasEnoughSpace(world, blockposition2)) {
+                if (world.getBiome(blockposition2).is(BiomeTags.WITHOUT_WANDERING_TRADER_SPAWNS)) {
                     return false;
                 }
 
-                WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(world, blockPos3, MobSpawnType.EVENT);
-                if (wanderingTrader != null) {
-                    for (int j = 0; j < 2; j++) {
-                        this.tryToSpawnLlamaFor(world, wanderingTrader, 4);
+                WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called
+
+                if (entityvillagertrader != null) {
+                    for (int i = 0; i < 2; ++i) {
+                        this.tryToSpawnLlamaFor(world, entityvillagertrader, 4);
                     }
 
-                    this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID());
-                    wanderingTrader.setDespawnDelay(48000);
-                    wanderingTrader.setWanderTarget(blockPos2);
-                    wanderingTrader.restrictTo(blockPos2, 16);
+                    this.serverLevelData.setWanderingTraderId(entityvillagertrader.getUUID());
+                    // entityvillagertrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent
+                    entityvillagertrader.setWanderTarget(blockposition1);
+                    entityvillagertrader.restrictTo(blockposition1, 16);
                     return true;
                 }
             }
@@ -119,41 +140,50 @@
     }
 
     private void tryToSpawnLlamaFor(ServerLevel world, WanderingTrader wanderingTrader, int range) {
-        BlockPos blockPos = this.findSpawnPositionNear(world, wanderingTrader.blockPosition(), range);
-        if (blockPos != null) {
-            TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(world, blockPos, MobSpawnType.EVENT);
-            if (traderLlama != null) {
-                traderLlama.setLeashedTo(wanderingTrader, true);
+        BlockPos blockposition = this.findSpawnPositionNear(world, wanderingTrader.blockPosition(), range);
+
+        if (blockposition != null) {
+            TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, MobSpawnType.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
+
+            if (entityllamatrader != null) {
+                entityllamatrader.setLeashedTo(wanderingTrader, true);
             }
         }
     }
 
     @Nullable
     private BlockPos findSpawnPositionNear(LevelReader world, BlockPos pos, int range) {
-        BlockPos blockPos = null;
-        SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.WANDERING_TRADER);
+        BlockPos blockposition1 = null;
+        SpawnPlacementType spawnplacementtype = SpawnPlacements.getPlacementType(EntityType.WANDERING_TRADER);
 
-        for (int i = 0; i < 10; i++) {
-            int j = pos.getX() + this.random.nextInt(range * 2) - range;
-            int k = pos.getZ() + this.random.nextInt(range * 2) - range;
-            int l = world.getHeight(Heightmap.Types.WORLD_SURFACE, j, k);
-            BlockPos blockPos2 = new BlockPos(j, l, k);
-            if (spawnPlacementType.isSpawnPositionOk(world, blockPos2, EntityType.WANDERING_TRADER)) {
-                blockPos = blockPos2;
+        for (int j = 0; j < 10; ++j) {
+            int k = pos.getX() + this.random.nextInt(range * 2) - range;
+            int l = pos.getZ() + this.random.nextInt(range * 2) - range;
+            int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l);
+            BlockPos blockposition2 = new BlockPos(k, i1, l);
+
+            if (spawnplacementtype.isSpawnPositionOk(world, blockposition2, EntityType.WANDERING_TRADER)) {
+                blockposition1 = blockposition2;
                 break;
             }
         }
 
-        return blockPos;
+        return blockposition1;
     }
 
     private boolean hasEnoughSpace(BlockGetter world, BlockPos pos) {
-        for (BlockPos blockPos : BlockPos.betweenClosed(pos, pos.offset(1, 2, 1))) {
-            if (!world.getBlockState(blockPos).getCollisionShape(world, blockPos).isEmpty()) {
-                return false;
+        Iterator iterator = BlockPos.betweenClosed(pos, pos.offset(1, 2, 1)).iterator();
+
+        BlockPos blockposition1;
+
+        do {
+            if (!iterator.hasNext()) {
+                return true;
             }
-        }
 
-        return true;
+            blockposition1 = (BlockPos) iterator.next();
+        } while (world.getBlockState(blockposition1).getCollisionShape(world, blockposition1).isEmpty());
+
+        return false;
     }
 }
