--- a/net/minecraft/world/level/SpawnerCreature.java
+++ b/net/minecraft/world/level/SpawnerCreature.java
@@ -47,6 +47,11 @@
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+// CraftBukkit start
+import net.minecraft.world.level.storage.WorldData;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+// CraftBukkit end
+
 public final class SpawnerCreature {
 
     private static final Logger LOGGER = LogManager.getLogger();
@@ -73,7 +78,8 @@
             if (entity instanceof EntityInsentient) {
                 EntityInsentient entityinsentient = (EntityInsentient) entity;
 
-                if (entityinsentient.isPersistent() || entityinsentient.isSpecialPersistence()) {
+                // CraftBukkit - Split out persistent check, don't apply it to special persistent mobs
+                if (entityinsentient.isTypeNotPersistent(0) && entityinsentient.isPersistent()) {
                     continue;
                 }
             }
@@ -108,10 +114,49 @@
         EnumCreatureType[] aenumcreaturetype = SpawnerCreature.SPAWNING_CATEGORIES;
         int i = aenumcreaturetype.length;
 
+        // CraftBukkit start - Other mob type spawn tick rate
+        WorldData worlddata = worldserver.getWorldData();
+        boolean spawnAnimalThisTick = worldserver.ticksPerAnimalSpawns != 0L && worlddata.getTime() % worldserver.ticksPerAnimalSpawns == 0L;
+        boolean spawnMonsterThisTick = worldserver.ticksPerMonsterSpawns != 0L && worlddata.getTime() % worldserver.ticksPerMonsterSpawns == 0L;
+        boolean spawnWaterThisTick = worldserver.ticksPerWaterSpawns != 0L && worlddata.getTime() % worldserver.ticksPerWaterSpawns == 0L;
+        boolean spawnAmbientThisTick = worldserver.ticksPerAmbientSpawns != 0L && worlddata.getTime() % worldserver.ticksPerAmbientSpawns == 0L;
+        boolean spawnWaterAmbientThisTick = worldserver.ticksPerWaterAmbientSpawns != 0L && worlddata.getTime() % worldserver.ticksPerWaterAmbientSpawns == 0L;
+        // CraftBukkit end
+
         for (int j = 0; j < i; ++j) {
             EnumCreatureType enumcreaturetype = aenumcreaturetype[j];
+            // CraftBukkit start - Use per-world spawn limits
+            boolean spawnThisTick = true;
+            int limit = enumcreaturetype.b();
+            switch (enumcreaturetype) {
+                case MONSTER:
+                    spawnThisTick = spawnMonsterThisTick;
+                    limit = worldserver.getWorld().getMonsterSpawnLimit();
+                    break;
+                case CREATURE:
+                    spawnThisTick = spawnAnimalThisTick;
+                    limit = worldserver.getWorld().getAnimalSpawnLimit();
+                    break;
+                case WATER_CREATURE:
+                    spawnThisTick = spawnWaterThisTick;
+                    limit = worldserver.getWorld().getWaterAnimalSpawnLimit();
+                    break;
+                case AMBIENT:
+                    spawnThisTick = spawnAmbientThisTick;
+                    limit = worldserver.getWorld().getAmbientSpawnLimit();
+                    break;
+                case WATER_AMBIENT:
+                    spawnThisTick = spawnWaterAmbientThisTick;
+                    limit = worldserver.getWorld().getWaterAmbientSpawnLimit();
+                    break;
+            }
 
-            if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype)) {
+            if (!spawnThisTick || limit == 0) {
+                continue;
+            }
+
+            if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype, limit)) {
+                // CraftBukkit end
                 Objects.requireNonNull(spawnercreature_d);
                 SpawnerCreature.c spawnercreature_c = spawnercreature_d::a;
 
@@ -196,10 +241,14 @@
                                         entityinsentient.setPositionRotation(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F);
                                         if (a(worldserver, entityinsentient, d2)) {
                                             groupdataentity = entityinsentient.prepare(worldserver, worldserver.getDamageScaler(entityinsentient.getChunkCoordinates()), EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null);
-                                            ++j;
-                                            ++k1;
-                                            worldserver.addAllEntities(entityinsentient);
-                                            spawnercreature_a.run(entityinsentient, ichunkaccess);
+                                            // CraftBukkit start
+                                            worldserver.addAllEntities(entityinsentient, SpawnReason.NATURAL);
+                                            if (!entityinsentient.isRemoved()) {
+                                                ++j;
+                                                ++k1;
+                                                spawnercreature_a.run(entityinsentient, ichunkaccess);
+                                            }
+                                            // CraftBukkit end
                                             if (j >= entityinsentient.getMaxSpawnGroup()) {
                                                 return;
                                             }
@@ -370,7 +419,7 @@
 
                                     if (entityinsentient.a((GeneratorAccess) worldaccess, EnumMobSpawn.CHUNK_GENERATION) && entityinsentient.a((IWorldReader) worldaccess)) {
                                         groupdataentity = entityinsentient.prepare(worldaccess, worldaccess.getDamageScaler(entityinsentient.getChunkCoordinates()), EnumMobSpawn.CHUNK_GENERATION, groupdataentity, (NBTTagCompound) null);
-                                        worldaccess.addAllEntities(entityinsentient);
+                                        worldaccess.addAllEntities(entityinsentient, SpawnReason.CHUNK_GEN); // CraftBukkit
                                         flag = true;
                                     }
                                 }
@@ -486,8 +535,10 @@
             return this.unmodifiableMobCategoryCounts;
         }
 
-        boolean a(EnumCreatureType enumcreaturetype) {
-            int i = enumcreaturetype.b() * this.spawnableChunkCount / SpawnerCreature.MAGIC_NUMBER;
+        // CraftBukkit start
+        boolean a(EnumCreatureType enumcreaturetype, int limit) {
+            int i = limit * this.spawnableChunkCount / SpawnerCreature.MAGIC_NUMBER;
+            // CraftBukkit end
 
             return this.mobCategoryCounts.getInt(enumcreaturetype) < i;
         }
