--- a/net/minecraft/world/level/BaseSpawner.java
+++ b/net/minecraft/world/level/BaseSpawner.java
@@ -53,13 +_,15 @@
     public int maxNearbyEntities = 6;
     public int requiredPlayerRange = 16;
     public int spawnRange = 4;
+    private int tickDelay = 0; // Paper - Configurable mob spawner tick rate
 
     public void setEntityId(EntityType<?> type, @Nullable Level level, RandomSource random, BlockPos pos) {
         this.getOrCreateNextSpawnData(level, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());
+        this.spawnPotentials = WeightedList.of(); // CraftBukkit - SPIGOT-3496, MC-92282
     }
 
     public boolean isNearPlayer(Level level, BlockPos pos) {
-        return level.hasNearbyAlivePlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange);
+        return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API
     }
 
     public void clientTick(Level level, BlockPos pos) {
@@ -82,13 +_,19 @@
     }
 
     public void serverTick(ServerLevel serverLevel, BlockPos pos) {
+        if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick
+        // Paper start - Configurable mob spawner tick rate
+        if (spawnDelay > 0 && --tickDelay > 0) return;
+        tickDelay = serverLevel.paperConfig().tickRates.mobSpawner;
+        if (tickDelay == -1) { return; } // If disabled
+        // Paper end - Configurable mob spawner tick rate
         if (this.isNearPlayer(serverLevel, pos)) {
-            if (this.spawnDelay == -1) {
+            if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate
                 this.delay(serverLevel, pos);
             }
 
             if (this.spawnDelay > 0) {
-                this.spawnDelay--;
+                this.spawnDelay -= tickDelay; // Paper - Configurable mob spawner tick rate
             } else {
                 boolean flag = false;
                 RandomSource random = serverLevel.getRandom();
@@ -128,6 +_,21 @@
                                 continue;
                             }
 
+                            // Paper start - PreCreatureSpawnEvent
+                            com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent(
+                                org.bukkit.craftbukkit.util.CraftLocation.toBukkit(vec3, serverLevel.getWorld()),
+                                org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()),
+                                org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel)
+                            );
+                            if (!event.callEvent()) {
+                                flag = true;
+                                if (event.shouldAbortSpawn()) {
+                                    break;
+                                }
+                                continue;
+                            }
+                            // Paper end - PreCreatureSpawnEvent
+
                             Entity entity = EntityType.loadEntityRecursive(valueInput, serverLevel, EntitySpawnReason.SPAWNER, entity1 -> {
                                 entity1.snapTo(vec3.x, vec3.y, vec3.z, entity1.getYRot(), entity1.getXRot());
                                 return entity1;
@@ -148,6 +_,7 @@
                                 return;
                             }
 
+                            entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag
                             entity.snapTo(entity.getX(), entity.getY(), entity.getZ(), random.nextFloat() * 360.0F, 0.0F);
                             if (entity instanceof Mob mob) {
                                 if (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER)
@@ -162,9 +_,22 @@
                                 }
 
                                 nextSpawnData.getEquipment().ifPresent(mob::equip);
+                                // Spigot start
+                                if (mob.level().spigotConfig.nerfSpawnerMobs) {
+                                    mob.aware = false;
+                                }
+                                // Spigot end
                             }
 
-                            if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
+                            // Paper start
+                            entity.spawnedViaMobSpawner = true;
+                            entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
+                            flag = true;
+                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
+                                continue;
+                            }
+                            if (!serverLevel.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
+                                // Paper end
                                 this.delay(serverLevel, pos);
                                 return;
                             }
@@ -175,7 +_,7 @@
                                 ((Mob)entity).spawnAnim();
                             }
 
-                            flag = true;
+                            //flag = true; // Paper - moved up above cancellable event
                         }
                     }
                 }
@@ -202,12 +_,14 @@
     }
 
     public void load(@Nullable Level level, BlockPos pos, ValueInput input) {
-        this.spawnDelay = input.getShortOr("Delay", (short)20);
+        this.spawnDelay = input.getIntOr("Paper.Delay", input.getShortOr("Delay", (short) 20)); // Paper - use int if set
         input.read("SpawnData", SpawnData.CODEC).ifPresent(spawnData -> this.setNextSpawnData(level, pos, spawnData));
         this.spawnPotentials = input.read("SpawnPotentials", SpawnData.LIST_CODEC)
             .orElseGet(() -> WeightedList.of(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData()));
-        this.minSpawnDelay = input.getIntOr("MinSpawnDelay", 200);
-        this.maxSpawnDelay = input.getIntOr("MaxSpawnDelay", 800);
+        // Paper start - use int if set
+        this.minSpawnDelay = input.getIntOr("Paper.MinSpawnDelay", input.getIntOr("MinSpawnDelay", 200));
+        this.maxSpawnDelay = input.getIntOr("Paper.MaxSpawnDelay", input.getIntOr("MaxSpawnDelay", 800));
+        // Paper end - use int if set
         this.spawnCount = input.getIntOr("SpawnCount", 4);
         this.maxNearbyEntities = input.getIntOr("MaxNearbyEntities", 6);
         this.requiredPlayerRange = input.getIntOr("RequiredPlayerRange", 16);
@@ -216,9 +_,19 @@
     }
 
     public void save(ValueOutput output) {
-        output.putShort("Delay", (short)this.spawnDelay);
-        output.putShort("MinSpawnDelay", (short)this.minSpawnDelay);
-        output.putShort("MaxSpawnDelay", (short)this.maxSpawnDelay);
+        // Paper start
+        if (this.spawnDelay > Short.MAX_VALUE) {
+            output.putInt("Paper.Delay", this.spawnDelay);
+        }
+        output.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay));
+
+        if (this.minSpawnDelay > Short.MAX_VALUE || this.maxSpawnDelay > Short.MAX_VALUE) {
+            output.putInt("Paper.MinSpawnDelay", this.minSpawnDelay);
+            output.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay);
+        }
+        output.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay));
+        output.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay));
+        // Paper end
         output.putShort("SpawnCount", (short)this.spawnCount);
         output.putShort("MaxNearbyEntities", (short)this.maxNearbyEntities);
         output.putShort("RequiredPlayerRange", (short)this.requiredPlayerRange);
