From 002d3ab1b52255ff8f24556258ccd094f3126600 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 14:11:08 -0700
Subject: [PATCH 01/10] Fix packed data reading in anti-xray

---
 Spigot-Server-Patches/fixup-Anti-Xray.patch | 54 +++++++++++++++++++++
 1 file changed, 54 insertions(+)
 create mode 100644 Spigot-Server-Patches/fixup-Anti-Xray.patch

diff --git a/Spigot-Server-Patches/fixup-Anti-Xray.patch b/Spigot-Server-Patches/fixup-Anti-Xray.patch
new file mode 100644
index 0000000000..7c88ddda14
--- /dev/null
+++ b/Spigot-Server-Patches/fixup-Anti-Xray.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 14:10:10 -0700
+Subject: [PATCH] fixup! Anti-Xray
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
+@@ -0,0 +0,0 @@ public final class DataBitsReader {
+         bitInLongIndex += bitsPerObject;
+ 
+         if (bitInLongIndex > 63) {
+-            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
+             longInDataBitsIndex += 8;
+             init();
+-
+-            if (bitInLongIndex > 0) {
+-                value |= current << bitsPerObject - bitInLongIndex & mask;
+-            }
+         }
+ 
+         return value;
+diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
+@@ -0,0 +0,0 @@ public final class DataBitsWriter {
+ 
+         if (bitInLongIndex > 63) {
+             finish();
+-            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
+             longInDataBitsIndex += 8;
+             init();
+-
+-            if (bitInLongIndex > 0) {
+-                current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex;
+-                dirty = true;
+-            }
+         }
+     }
+ 
+@@ -0,0 +0,0 @@ public final class DataBitsWriter {
+ 
+         if (bitInLongIndex > 63) {
+             finish();
+-            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
+             longInDataBitsIndex += 8;
+             init();
+         }

From 6e59ab0eb590d1ad42c24533391293ca91901248 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 14:31:38 -0700
Subject: [PATCH 02/10] Fix the entity knockback by entity patch

---
 ...plement-EntityKnockbackByEntityEvent.patch | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch

diff --git a/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch b/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch
new file mode 100644
index 0000000000..3420c57883
--- /dev/null
+++ b/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch
@@ -0,0 +1,93 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 14:28:49 -0700
+Subject: [PATCH] fixup! Implement EntityKnockbackByEntityEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/EntityHuman.java
++++ b/src/main/java/net/minecraft/server/EntityHuman.java
+@@ -0,0 +0,0 @@ public abstract class EntityHuman extends EntityLiving {
+                     if (flag5) {
+                         if (i > 0) {
+                             if (entity instanceof EntityLiving) {
+-                                ((EntityLiving) entity).knockingBackEntity = this; // Paper
+-                                ((EntityLiving) entity).a((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
+-                                ((EntityLiving) entity).knockingBackEntity = null; // Paper
++                                ((EntityLiving) entity).doKnockback((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper
+                             } else {
+                                 entity.h((double) (-MathHelper.sin(this.yaw * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (MathHelper.cos(this.yaw * 0.017453292F) * (float) i * 0.5F));
+                             }
+@@ -0,0 +0,0 @@ public abstract class EntityHuman extends EntityLiving {
+                                 if (entityliving != this && entityliving != entity && !this.r(entityliving) && (!(entityliving instanceof EntityArmorStand) || !((EntityArmorStand) entityliving).isMarker()) && this.h((Entity) entityliving) < 9.0D) {
+                                     // CraftBukkit start - Only apply knockback if the damage hits
+                                     if (entityliving.damageEntity(DamageSource.playerAttack(this).sweep(), f4)) {
+-                                        ((EntityLiving) entity).knockingBackEntity = this; // Paper
+-                                    entityliving.a(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
+-                                        ((EntityLiving) entity).knockingBackEntity = null; // Paper
++                                    entityliving.doKnockback(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
+                                     }
+                                     // CraftBukkit end
+                                 }
+diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/EntityInsentient.java
++++ b/src/main/java/net/minecraft/server/EntityInsentient.java
+@@ -0,0 +0,0 @@ public abstract class EntityInsentient extends EntityLiving {
+ 
+         if (flag) {
+             if (f1 > 0.0F && entity instanceof EntityLiving) {
+-                ((EntityLiving) entity).knockingBackEntity = this; // Paper
+-                ((EntityLiving) entity).a(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
+-                ((EntityLiving) entity).knockingBackEntity = null; // Paper
++                ((EntityLiving) entity).doKnockback(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
+                 this.setMot(this.getMot().d(0.6D, 1.0D, 0.6D));
+             }
+ 
+diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/EntityLiving.java
++++ b/src/main/java/net/minecraft/server/EntityLiving.java
+@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
+                     }
+ 
+                     this.aw = (float) (MathHelper.d(d1, d0) * 57.2957763671875D - (double) this.yaw);
+-                    this.knockingBackEntity = entity1 instanceof EntityLiving ? ((EntityLiving) entity1) : null; // Paper
+-                    this.a(0.4F, d0, d1);
+-                    this.knockingBackEntity = null; // Paper
++                    this.doKnockback(0.4F, d0, d1, entity1); // Paper
+                 } else {
+                     this.aw = (float) ((int) (Math.random() * 2.0D) * 180);
+                 }
+@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
+     }
+ 
+     protected void f(EntityLiving entityliving) {
+-        ((EntityLiving) entityliving).knockingBackEntity = this; // Paper
+-        entityliving.a(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ());
+-        ((EntityLiving) entityliving).knockingBackEntity = null; // Paper
++        entityliving.doKnockback(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ(), this);
+     }
+ 
+     private boolean f(DamageSource damagesource) {
+@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
+     }
+ 
+     public void a(float f, double d0, double d1) {
++        // Paper start - add knockbacking entity parameter
++        this.doKnockback(f, d0, d1, null);
++    }
++    public void doKnockback(float f, double d0, double d1, Entity knockingBackEntity) {
++        // Paper end - add knockbacking entity parameter
+         f = (float) ((double) f * (1.0D - this.b(GenericAttributes.KNOCKBACK_RESISTANCE)));
+         if (f > 0.0F) {
+             this.impulse = true;
+@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
+             // Paper end
+         }
+     }
+-    EntityLiving knockingBackEntity; // Paper
+ 
+     @Nullable
+     protected SoundEffect getSoundHurt(DamageSource damagesource) {

From 9b3bb0cf51a89e4fc9cb90b30e1c6663b71a2db4 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 14:33:49 -0700
Subject: [PATCH 03/10] Pass predicate from default get hard colliding entities
 method

---
 Spigot-Server-Patches/Anti-Xray.patch         | 15 +--
 ...plement-EntityKnockbackByEntityEvent.patch | 40 ++++----
 ...imise-entity-hard-collision-checking.patch |  2 +-
 Spigot-Server-Patches/fixup-Anti-Xray.patch   | 54 -----------
 ...plement-EntityKnockbackByEntityEvent.patch | 93 -------------------
 5 files changed, 26 insertions(+), 178 deletions(-)
 delete mode 100644 Spigot-Server-Patches/fixup-Anti-Xray.patch
 delete mode 100644 Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch

diff --git a/Spigot-Server-Patches/Anti-Xray.patch b/Spigot-Server-Patches/Anti-Xray.patch
index 29e5b2e0e2..2ce6dcd9e1 100644
--- a/Spigot-Server-Patches/Anti-Xray.patch
+++ b/Spigot-Server-Patches/Anti-Xray.patch
@@ -895,13 +895,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        bitInLongIndex += bitsPerObject;
 +
 +        if (bitInLongIndex > 63) {
-+            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
 +            longInDataBitsIndex += 8;
 +            init();
-+
-+            if (bitInLongIndex > 0) {
-+                value |= current << bitsPerObject - bitInLongIndex & mask;
-+            }
 +        }
 +
 +        return value;
@@ -975,14 +971,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        if (bitInLongIndex > 63) {
 +            finish();
-+            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
 +            longInDataBitsIndex += 8;
 +            init();
-+
-+            if (bitInLongIndex > 0) {
-+                current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex;
-+                dirty = true;
-+            }
 +        }
 +    }
 +
@@ -991,7 +982,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        if (bitInLongIndex > 63) {
 +            finish();
-+            bitInLongIndex -= 64;
++            bitInLongIndex = 0;
 +            longInDataBitsIndex += 8;
 +            init();
 +        }
diff --git a/Spigot-Server-Patches/Implement-EntityKnockbackByEntityEvent.patch b/Spigot-Server-Patches/Implement-EntityKnockbackByEntityEvent.patch
index 957fe3c9b1..8b7ed21d67 100644
--- a/Spigot-Server-Patches/Implement-EntityKnockbackByEntityEvent.patch
+++ b/Spigot-Server-Patches/Implement-EntityKnockbackByEntityEvent.patch
@@ -13,9 +13,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      if (flag5) {
                          if (i > 0) {
                              if (entity instanceof EntityLiving) {
-+                                ((EntityLiving) entity).knockingBackEntity = this; // Paper
-                                 ((EntityLiving) entity).a((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
-+                                ((EntityLiving) entity).knockingBackEntity = null; // Paper
+-                                ((EntityLiving) entity).a((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
++                                ((EntityLiving) entity).doKnockback((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper
                              } else {
                                  entity.h((double) (-MathHelper.sin(this.yaw * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (MathHelper.cos(this.yaw * 0.017453292F) * (float) i * 0.5F));
                              }
@@ -23,9 +22,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                                  if (entityliving != this && entityliving != entity && !this.r(entityliving) && (!(entityliving instanceof EntityArmorStand) || !((EntityArmorStand) entityliving).isMarker()) && this.h((Entity) entityliving) < 9.0D) {
                                      // CraftBukkit start - Only apply knockback if the damage hits
                                      if (entityliving.damageEntity(DamageSource.playerAttack(this).sweep(), f4)) {
-+                                        ((EntityLiving) entity).knockingBackEntity = this; // Paper
-                                     entityliving.a(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
-+                                        ((EntityLiving) entity).knockingBackEntity = null; // Paper
+-                                    entityliving.a(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
++                                    entityliving.doKnockback(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
                                      }
                                      // CraftBukkit end
                                  }
@@ -37,9 +35,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          if (flag) {
              if (f1 > 0.0F && entity instanceof EntityLiving) {
-+                ((EntityLiving) entity).knockingBackEntity = this; // Paper
-                 ((EntityLiving) entity).a(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
-+                ((EntityLiving) entity).knockingBackEntity = null; // Paper
+-                ((EntityLiving) entity).a(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
++                ((EntityLiving) entity).doKnockback(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
                  this.setMot(this.getMot().d(0.6D, 1.0D, 0.6D));
              }
  
@@ -51,9 +48,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      }
  
                      this.aw = (float) (MathHelper.d(d1, d0) * 57.2957763671875D - (double) this.yaw);
-+                    this.knockingBackEntity = entity1 instanceof EntityLiving ? ((EntityLiving) entity1) : null; // Paper
-                     this.a(0.4F, d0, d1);
-+                    this.knockingBackEntity = null; // Paper
+-                    this.a(0.4F, d0, d1);
++                    this.doKnockback(0.4F, d0, d1, entity1); // Paper
                  } else {
                      this.aw = (float) ((int) (Math.random() * 2.0D) * 180);
                  }
@@ -61,12 +57,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      protected void f(EntityLiving entityliving) {
-+        ((EntityLiving) entityliving).knockingBackEntity = this; // Paper
-         entityliving.a(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ());
-+        ((EntityLiving) entityliving).knockingBackEntity = null; // Paper
+-        entityliving.a(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ());
++        entityliving.doKnockback(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ(), this);
      }
  
      private boolean f(DamageSource damagesource) {
+@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
+     }
+ 
+     public void a(float f, double d0, double d1) {
++        // Paper start - add knockbacking entity parameter
++        this.doKnockback(f, d0, d1, null);
++    }
++    public void doKnockback(float f, double d0, double d1, Entity knockingBackEntity) {
++        // Paper end - add knockbacking entity parameter
+         f = (float) ((double) f * (1.0D - this.b(GenericAttributes.KNOCKBACK_RESISTANCE)));
+         if (f > 0.0F) {
+             this.impulse = true;
 @@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
              Vec3D vec3d1 = (new Vec3D(d0, 0.0D, d1)).d().a((double) f);
  
@@ -83,7 +90,4 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // Paper end
          }
      }
-+    EntityLiving knockingBackEntity; // Paper
  
-     @Nullable
-     protected SoundEffect getSoundHurt(DamageSource damagesource) {
diff --git a/Spigot-Server-Patches/Optimise-entity-hard-collision-checking.patch b/Spigot-Server-Patches/Optimise-entity-hard-collision-checking.patch
index e809bb7285..0bd90bb9f8 100644
--- a/Spigot-Server-Patches/Optimise-entity-hard-collision-checking.patch
+++ b/Spigot-Server-Patches/Optimise-entity-hard-collision-checking.patch
@@ -157,7 +157,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * Not guaranteed to only return hard colliding entites
 +     */
 +    default List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
-+        return this.getEntities(entity, axisalignedbb);
++        return this.getEntities(entity, axisalignedbb, predicate);
 +    }
 +    // Paper end - optimise hard collision
 +
diff --git a/Spigot-Server-Patches/fixup-Anti-Xray.patch b/Spigot-Server-Patches/fixup-Anti-Xray.patch
deleted file mode 100644
index 7c88ddda14..0000000000
--- a/Spigot-Server-Patches/fixup-Anti-Xray.patch
+++ /dev/null
@@ -1,54 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 14:10:10 -0700
-Subject: [PATCH] fixup! Anti-Xray
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
-+++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java
-@@ -0,0 +0,0 @@ public final class DataBitsReader {
-         bitInLongIndex += bitsPerObject;
- 
-         if (bitInLongIndex > 63) {
--            bitInLongIndex -= 64;
-+            bitInLongIndex = 0;
-             longInDataBitsIndex += 8;
-             init();
--
--            if (bitInLongIndex > 0) {
--                value |= current << bitsPerObject - bitInLongIndex & mask;
--            }
-         }
- 
-         return value;
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
-+++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java
-@@ -0,0 +0,0 @@ public final class DataBitsWriter {
- 
-         if (bitInLongIndex > 63) {
-             finish();
--            bitInLongIndex -= 64;
-+            bitInLongIndex = 0;
-             longInDataBitsIndex += 8;
-             init();
--
--            if (bitInLongIndex > 0) {
--                current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex;
--                dirty = true;
--            }
-         }
-     }
- 
-@@ -0,0 +0,0 @@ public final class DataBitsWriter {
- 
-         if (bitInLongIndex > 63) {
-             finish();
--            bitInLongIndex -= 64;
-+            bitInLongIndex = 0;
-             longInDataBitsIndex += 8;
-             init();
-         }
diff --git a/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch b/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch
deleted file mode 100644
index 3420c57883..0000000000
--- a/Spigot-Server-Patches/fixup-Implement-EntityKnockbackByEntityEvent.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 14:28:49 -0700
-Subject: [PATCH] fixup! Implement EntityKnockbackByEntityEvent
-
-
-diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/EntityHuman.java
-+++ b/src/main/java/net/minecraft/server/EntityHuman.java
-@@ -0,0 +0,0 @@ public abstract class EntityHuman extends EntityLiving {
-                     if (flag5) {
-                         if (i > 0) {
-                             if (entity instanceof EntityLiving) {
--                                ((EntityLiving) entity).knockingBackEntity = this; // Paper
--                                ((EntityLiving) entity).a((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
--                                ((EntityLiving) entity).knockingBackEntity = null; // Paper
-+                                ((EntityLiving) entity).doKnockback((float) i * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this); // Paper
-                             } else {
-                                 entity.h((double) (-MathHelper.sin(this.yaw * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (MathHelper.cos(this.yaw * 0.017453292F) * (float) i * 0.5F));
-                             }
-@@ -0,0 +0,0 @@ public abstract class EntityHuman extends EntityLiving {
-                                 if (entityliving != this && entityliving != entity && !this.r(entityliving) && (!(entityliving instanceof EntityArmorStand) || !((EntityArmorStand) entityliving).isMarker()) && this.h((Entity) entityliving) < 9.0D) {
-                                     // CraftBukkit start - Only apply knockback if the damage hits
-                                     if (entityliving.damageEntity(DamageSource.playerAttack(this).sweep(), f4)) {
--                                        ((EntityLiving) entity).knockingBackEntity = this; // Paper
--                                    entityliving.a(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
--                                        ((EntityLiving) entity).knockingBackEntity = null; // Paper
-+                                    entityliving.doKnockback(0.4F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
-                                     }
-                                     // CraftBukkit end
-                                 }
-diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/EntityInsentient.java
-+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
-@@ -0,0 +0,0 @@ public abstract class EntityInsentient extends EntityLiving {
- 
-         if (flag) {
-             if (f1 > 0.0F && entity instanceof EntityLiving) {
--                ((EntityLiving) entity).knockingBackEntity = this; // Paper
--                ((EntityLiving) entity).a(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)));
--                ((EntityLiving) entity).knockingBackEntity = null; // Paper
-+                ((EntityLiving) entity).doKnockback(f1 * 0.5F, (double) MathHelper.sin(this.yaw * 0.017453292F), (double) (-MathHelper.cos(this.yaw * 0.017453292F)), this);
-                 this.setMot(this.getMot().d(0.6D, 1.0D, 0.6D));
-             }
- 
-diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/EntityLiving.java
-+++ b/src/main/java/net/minecraft/server/EntityLiving.java
-@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
-                     }
- 
-                     this.aw = (float) (MathHelper.d(d1, d0) * 57.2957763671875D - (double) this.yaw);
--                    this.knockingBackEntity = entity1 instanceof EntityLiving ? ((EntityLiving) entity1) : null; // Paper
--                    this.a(0.4F, d0, d1);
--                    this.knockingBackEntity = null; // Paper
-+                    this.doKnockback(0.4F, d0, d1, entity1); // Paper
-                 } else {
-                     this.aw = (float) ((int) (Math.random() * 2.0D) * 180);
-                 }
-@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
-     }
- 
-     protected void f(EntityLiving entityliving) {
--        ((EntityLiving) entityliving).knockingBackEntity = this; // Paper
--        entityliving.a(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ());
--        ((EntityLiving) entityliving).knockingBackEntity = null; // Paper
-+        entityliving.doKnockback(0.5F, entityliving.locX() - this.locX(), entityliving.locZ() - this.locZ(), this);
-     }
- 
-     private boolean f(DamageSource damagesource) {
-@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
-     }
- 
-     public void a(float f, double d0, double d1) {
-+        // Paper start - add knockbacking entity parameter
-+        this.doKnockback(f, d0, d1, null);
-+    }
-+    public void doKnockback(float f, double d0, double d1, Entity knockingBackEntity) {
-+        // Paper end - add knockbacking entity parameter
-         f = (float) ((double) f * (1.0D - this.b(GenericAttributes.KNOCKBACK_RESISTANCE)));
-         if (f > 0.0F) {
-             this.impulse = true;
-@@ -0,0 +0,0 @@ public abstract class EntityLiving extends Entity {
-             // Paper end
-         }
-     }
--    EntityLiving knockingBackEntity; // Paper
- 
-     @Nullable
-     protected SoundEffect getSoundHurt(DamageSource damagesource) {

From cd6b64113f7a0744959cd1d6e2adf02728b78c01 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 14:36:48 -0700
Subject: [PATCH 04/10] Fix the piston duplication option when it is on

---
 .../Fix-piston-physics-inconsistency-MC-188840.patch           | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Spigot-Server-Patches/Fix-piston-physics-inconsistency-MC-188840.patch b/Spigot-Server-Patches/Fix-piston-physics-inconsistency-MC-188840.patch
index 665a490241..583a4a392b 100644
--- a/Spigot-Server-Patches/Fix-piston-physics-inconsistency-MC-188840.patch
+++ b/Spigot-Server-Patches/Fix-piston-physics-inconsistency-MC-188840.patch
@@ -63,12 +63,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  blockposition3 = blockposition3.shift(enumdirection1);
                  map.remove(blockposition3);
                  world.setTypeAndData(blockposition3, (IBlockData) Blocks.MOVING_PISTON.getBlockData().set(BlockPiston.FACING, enumdirection), 68);
+-                world.setTileEntity(blockposition3, BlockPistonMoving.a((IBlockData) list1.get(k), enumdirection, flag, false));
 +                // Paper start - fix a variety of piston desync dupes
 +                if (!allowDesync) {
 +                    iblockdata1 = world.getType(oldPos);
 +                    map.replace(oldPos, iblockdata1);
 +                }
-                 world.setTileEntity(blockposition3, BlockPistonMoving.a((IBlockData) list1.get(k), enumdirection, flag, false));
++                world.setTileEntity(blockposition3, BlockPistonMoving.a(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false));
 +                if (!allowDesync) {
 +                    world.setTypeAndData(oldPos, Blocks.AIR.getBlockData(), 2 | 4 | 16 | 1024); // set air to prevent later physics updates from seeing this block
 +                }

From a4f75b1ae07597b89ff96d94ca3dff849ce60a3f Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 15:54:39 -0700
Subject: [PATCH 05/10] fix recursion for leashing an abstract horse

---
 Spigot-Server-Patches/Undead-horse-leashing.patch | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/Spigot-Server-Patches/Undead-horse-leashing.patch b/Spigot-Server-Patches/Undead-horse-leashing.patch
index 301d9e4d57..ab489e5730 100644
--- a/Spigot-Server-Patches/Undead-horse-leashing.patch
+++ b/Spigot-Server-Patches/Undead-horse-leashing.patch
@@ -29,7 +29,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
 +    // Paper start
 +    @Override
-+    public boolean canLeash(EntityHuman entityhuman) {
++    public boolean a(EntityHuman entityhuman) {
 +        return world.paperConfig.allowLeashingUndeadHorse ? super.a(entityhuman) : super.a(entityhuman) && this.getMonsterType() != EnumMonsterType.UNDEAD; // Paper
 +    }
 +    // Paper end
@@ -42,14 +42,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/server/EntityInsentient.java
 +++ b/src/main/java/net/minecraft/server/EntityInsentient.java
 @@ -0,0 +0,0 @@ public abstract class EntityInsentient extends EntityLiving {
+ 
      }
  
-     public boolean a(EntityHuman entityhuman) {
-+        // Paper start - allow overriding
-+        return this.canLeash(entityhuman);
-+    }
-+    public boolean canLeash(EntityHuman entityhuman) {
-+        // Paper end - allow overriding
+-    public boolean a(EntityHuman entityhuman) {
++    public boolean a(EntityHuman entityhuman) { // Paper - overriden in EntityHorseAbstract
          return !this.isLeashed() && !(this instanceof IMonster);
      }
  

From 8088a420294bf50dc95cfdd678eee0319b5dc7d7 Mon Sep 17 00:00:00 2001
From: stonar96 <minecraft.stonar96@gmail.com>
Date: Sat, 27 Jun 2020 04:27:03 +0200
Subject: [PATCH 06/10] Fix Anti-Xray

---
 Spigot-Server-Patches/Anti-Xray.patch | 36 ++++++++++-----------------
 1 file changed, 13 insertions(+), 23 deletions(-)

diff --git a/Spigot-Server-Patches/Anti-Xray.patch b/Spigot-Server-Patches/Anti-Xray.patch
index 29e5b2e0e2..facbe05cd0 100644
--- a/Spigot-Server-Patches/Anti-Xray.patch
+++ b/Spigot-Server-Patches/Anti-Xray.patch
@@ -891,19 +891,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    public int read() {
-+        int value = (int) (current >>> bitInLongIndex) & mask;
-+        bitInLongIndex += bitsPerObject;
-+
-+        if (bitInLongIndex > 63) {
-+            bitInLongIndex -= 64;
++        if (bitInLongIndex + bitsPerObject > 64) {
++            bitInLongIndex = 0;
 +            longInDataBitsIndex += 8;
 +            init();
-+
-+            if (bitInLongIndex > 0) {
-+                value |= current << bitsPerObject - bitInLongIndex & mask;
-+            }
 +        }
 +
++        int value = (int) (current >>> bitInLongIndex) & mask;
++        bitInLongIndex += bitsPerObject;
 +        return value;
 +    }
 +}
@@ -969,29 +964,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    public void write(int value) {
++        if (bitInLongIndex + bitsPerObject > 64) {
++            finish();
++            bitInLongIndex = 0;
++            longInDataBitsIndex += 8;
++            init();
++        }
++
 +        current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
 +        dirty = true;
 +        bitInLongIndex += bitsPerObject;
-+
-+        if (bitInLongIndex > 63) {
-+            finish();
-+            bitInLongIndex -= 64;
-+            longInDataBitsIndex += 8;
-+            init();
-+
-+            if (bitInLongIndex > 0) {
-+                current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex;
-+                dirty = true;
-+            }
-+        }
 +    }
 +
 +    public void skip() {
 +        bitInLongIndex += bitsPerObject;
 +
-+        if (bitInLongIndex > 63) {
++        if (bitInLongIndex > 64) {
 +            finish();
-+            bitInLongIndex -= 64;
++            bitInLongIndex = bitsPerObject;
 +            longInDataBitsIndex += 8;
 +            init();
 +        }

From faf7dda51c44add0bd00b49406f456017837ebac Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 20:05:12 -0700
Subject: [PATCH 07/10] fix async chunk loading disregardling light data

---
 Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch
index b4bb915da0..53ce84e8ff 100644
--- a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch
+++ b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch
@@ -3241,6 +3241,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                if (ioThrowable != null) {
 +                    com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable);
 +                }
++                chunkHolder.tasks.forEach(Runnable::run);
 +                // Paper end
  
 -                if (nbttagcompound != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings

From a8624a2d1ad5f051f007973a6b0c65f1e5398903 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 21:57:36 -0700
Subject: [PATCH 08/10] Re-Add per player mob spawning

---
 .../Generator-Settings.patch                  |   4 +-
 ...k-Priority-Urgency-System-for-Chunks.patch |   6 +-
 ...No-Tick-view-distance-implementation.patch |   2 +-
 ...-isOutsideRange-to-use-distance-maps.patch |   5 +
 ...tance-map-to-optimise-entity-tracker.patch |   4 +-
 ...ement-optional-per-player-mob-spawns.patch | 823 ++++++++++++++++++
 6 files changed, 836 insertions(+), 8 deletions(-)
 create mode 100644 Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch

diff --git a/Spigot-Server-Patches/Generator-Settings.patch b/Spigot-Server-Patches/Generator-Settings.patch
index 9a058217ab..f4cda5030d 100644
--- a/Spigot-Server-Patches/Generator-Settings.patch
+++ b/Spigot-Server-Patches/Generator-Settings.patch
@@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 @@ -0,0 +0,0 @@ public class PaperWorldConfig {
-             }
-         }
+     private void perPlayerMobSpawns() {
+         perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false);
      }
 +
 +    public boolean generateFlatBedrock;
diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
index ff59d05fa0..ee4cbb798c 100644
--- a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
+++ b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
@@ -587,8 +587,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private int lastFoodSent = -99999999;
      private boolean lastSentSaturationZero = true;
 @@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
-         this.canPickUpLoot = true;
          this.maxHealthCache = this.getMaxHealth();
+         this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
      }
 +    // Paper start
 +    public BlockPosition getPointInFront(double inFront) {
@@ -1069,8 +1069,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end
  
-     private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER
-     private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) {
+     public void updatePlayerMobTypeMap(Entity entity) {
+         if (!this.world.paperConfig.perPlayerMobSpawns) {
 @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
          List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList();
          int j = chunkcoordintpair.x;
diff --git a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch
index b497bd6820..47ac87a183 100644
--- a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch
+++ b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch
@@ -353,7 +353,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - no-tick view distance
      }
  
-     private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER
+     public void updatePlayerMobTypeMap(Entity entity) {
 @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
          completablefuture1.thenAcceptAsync((either) -> {
              either.mapLeft((chunk) -> {
diff --git a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch
index bcb3df5f67..a536028223 100644
--- a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch
+++ b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch
@@ -290,6 +290,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - optimise PlayerChunkMap#isOutsideRange
      }
  
+     public void updatePlayerMobTypeMap(Entity entity) {
+@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+         return entityPlayer.mobCounts[enumCreatureType.ordinal()];
+     }
+ 
 +    private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER
      private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) {
          double d0 = (double) (chunkcoordintpair.x * 16 + 8);
diff --git a/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch b/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch
index 66bc7675c8..2a7b598adc 100644
--- a/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch
+++ b/Spigot-Server-Patches/Use-distance-map-to-optimise-entity-tracker.patch
@@ -117,9 +117,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // Paper end
  
 @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
-         this.l = supplier;
          this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper
          this.setViewDistance(i);
+         this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
 +        // Paper start - use distance map to optimise entity tracker
 +        this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
 +        this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
@@ -161,7 +161,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - use distance map to optimise entity tracker
      }
  
-     private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) {
+     public void updatePlayerMobTypeMap(Entity entity) {
 @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
      }
  
diff --git a/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch b/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch
new file mode 100644
index 0000000000..187c91562a
--- /dev/null
+++ b/Spigot-Server-Patches/implement-optional-per-player-mob-spawns.patch
@@ -0,0 +1,823 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: kickash32 <kickash32@gmail.com>
+Date: Mon, 19 Aug 2019 01:27:58 +0500
+Subject: [PATCH] implement optional per player mob spawns
+
+
+diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+@@ -0,0 +0,0 @@ public class WorldTimingsHandler {
+ 
+ 
+     public final Timing miscMobSpawning;
++    public final Timing playerMobDistanceMapUpdate;
+ 
+     public final Timing poiUnload;
+     public final Timing chunkUnload;
+@@ -0,0 +0,0 @@ public class WorldTimingsHandler {
+ 
+ 
+         miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
++        playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update");
+ 
+         poiUnload = Timings.ofSafe(name + "Chunk unload - POI");
+         chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk");
+diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+@@ -0,0 +0,0 @@ public class PaperWorldConfig {
+             }
+         }
+     }
++
++    public boolean perPlayerMobSpawns = false;
++    private void perPlayerMobSpawns() {
++        perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false);
++    }
+ }
+diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.util;
++
++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
++import net.minecraft.server.ChunkCoordIntPair;
++import net.minecraft.server.EntityPlayer;
++import net.minecraft.server.SectionPosition;
++import org.spigotmc.AsyncCatcher;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
++
++/** @author Spottedleaf */
++public final class PlayerMobDistanceMap {
++
++    private static final PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>();
++
++    private final Map<EntityPlayer, SectionPosition> players = new HashMap<>();
++    // we use linked for better iteration.
++    private final Long2ObjectOpenHashMap<PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer>> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f);
++    private int viewDistance;
++
++    private final PooledHashSets<EntityPlayer> pooledHashSets = new PooledHashSets<>();
++
++    public PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> getPlayersInRange(final ChunkCoordIntPair chunkPos) {
++        return this.getPlayersInRange(chunkPos.x, chunkPos.z);
++    }
++
++    public PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> getPlayersInRange(final int chunkX, final int chunkZ) {
++        return this.playerMap.getOrDefault(ChunkCoordIntPair.pair(chunkX, chunkZ), EMPTY_SET);
++    }
++
++    public void update(final List<EntityPlayer> currentPlayers, final int newViewDistance) {
++        AsyncCatcher.catchOp("Distance map update");
++        final ObjectLinkedOpenHashSet<EntityPlayer> gone = new ObjectLinkedOpenHashSet<>(this.players.keySet());
++
++        final int oldViewDistance = this.viewDistance;
++        this.viewDistance = newViewDistance;
++
++        for (final EntityPlayer player : currentPlayers) {
++            if (player.isSpectator() || !player.affectsSpawning) {
++                continue; // will be left in 'gone' (or not added at all)
++            }
++
++            gone.remove(player);
++
++            final SectionPosition newPosition = player.getPlayerMapSection();
++            final SectionPosition oldPosition = this.players.put(player, newPosition);
++
++            if (oldPosition == null) {
++                this.addNewPlayer(player, newPosition, newViewDistance);
++            } else {
++                this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance);
++            }
++            //this.validatePlayer(player, newViewDistance); // debug only
++        }
++
++        for (final EntityPlayer player : gone) {
++            final SectionPosition oldPosition = this.players.remove(player);
++            if (oldPosition != null) {
++                this.removePlayer(player, oldPosition, oldViewDistance);
++            }
++        }
++    }
++
++    // expensive op, only for debug
++    private void validatePlayer(final EntityPlayer player, final int viewDistance) {
++        int entiesGot = 0;
++        int expectedEntries = (2 * viewDistance + 1);
++        expectedEntries *= expectedEntries;
++
++        final SectionPosition currPosition = player.getPlayerMapSection();
++
++        final int centerX = currPosition.getX();
++        final int centerZ = currPosition.getZ();
++
++        for (final Long2ObjectLinkedOpenHashMap.Entry<PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer>> entry : this.playerMap.long2ObjectEntrySet()) {
++            final long key = entry.getLongKey();
++            final PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> map = entry.getValue();
++
++            if (map.referenceCount == 0) {
++                throw new IllegalStateException("Invalid map");
++            }
++
++            if (map.set.contains(player)) {
++                ++entiesGot;
++
++                final int chunkX = ChunkCoordIntPair.getX(key);
++                final int chunkZ = ChunkCoordIntPair.getZ(key);
++
++                final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ));
++
++                if (dist > viewDistance) {
++                    throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist);
++                }
++            }
++        }
++
++        if (entiesGot != expectedEntries) {
++            throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot);
++        }
++    }
++
++    private void addPlayerTo(final EntityPlayer player, final int chunkX, final int chunkZ) {
++       this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players) -> {
++           if (players == null) {
++               return player.cachedSingleMobDistanceMap;
++           } else {
++               return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player);
++           }
++        });
++    }
++
++    private void removePlayerFrom(final EntityPlayer player, final int chunkX, final int chunkZ) {
++        this.playerMap.compute(ChunkCoordIntPair.pair(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players) -> {
++            return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map
++        });
++    }
++
++    private void updatePlayer(final EntityPlayer player, final SectionPosition oldPosition, final SectionPosition newPosition, final int oldViewDistance, final int newViewDistance) {
++        final int toX = newPosition.getX();
++        final int toZ = newPosition.getZ();
++        final int fromX = oldPosition.getX();
++        final int fromZ = oldPosition.getZ();
++
++        final int dx = toX - fromX;
++        final int dz = toZ - fromZ;
++
++        final int totalX = Math.abs(fromX - toX);
++        final int totalZ = Math.abs(fromZ - toZ);
++
++        if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) {
++            // teleported?
++            this.removePlayer(player, oldPosition, oldViewDistance);
++            this.addNewPlayer(player, newPosition, newViewDistance);
++            return;
++        }
++
++        // x axis is width
++        // z axis is height
++        // right refers to the x axis of where we moved
++        // top refers to the z axis of where we moved
++
++        if (oldViewDistance == newViewDistance) {
++            // same view distance
++
++            // used for relative positioning
++            final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise
++            final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise
++
++            // The area excluded by overlapping the two view distance squares creates four rectangles:
++            // Two on the left, and two on the right. The ones on the left we consider the "removed" section
++            // and on the right the "added" section.
++            // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
++            // exclusive to the regions they surround.
++
++            // 4 points of the rectangle
++            int maxX; // exclusive
++            int minX; // inclusive
++            int maxZ; // exclusive
++            int minZ; // inclusive
++
++            if (dx != 0) {
++                // handle right addition
++
++                maxX = toX + (oldViewDistance * right) + right; // exclusive
++                minX = fromX + (oldViewDistance * right) + right; // inclusive
++                maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
++                minZ = toZ - (oldViewDistance * up); // inclusive
++
++                for (int currX = minX; currX != maxX; currX += right) {
++                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
++                        this.addPlayerTo(player, currX, currZ);
++                    }
++                }
++            }
++
++            if (dz != 0) {
++                // handle up addition
++
++                maxX = toX + (oldViewDistance * right) + right; // exclusive
++                minX = toX - (oldViewDistance * right); // inclusive
++                maxZ = toZ + (oldViewDistance * up) + up; // exclusive
++                minZ = fromZ + (oldViewDistance * up) + up; // inclusive
++
++                for (int currX = minX; currX != maxX; currX += right) {
++                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
++                        this.addPlayerTo(player, currX, currZ);
++                    }
++                }
++            }
++
++            if (dx != 0) {
++                // handle left removal
++
++                maxX = toX - (oldViewDistance * right); // exclusive
++                minX = fromX - (oldViewDistance * right); // inclusive
++                maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
++                minZ = toZ - (oldViewDistance * up); // inclusive
++
++                for (int currX = minX; currX != maxX; currX += right) {
++                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
++                        this.removePlayerFrom(player, currX, currZ);
++                    }
++                }
++            }
++
++            if (dz != 0) {
++                // handle down removal
++
++                maxX = fromX + (oldViewDistance * right) + right; // exclusive
++                minX = fromX - (oldViewDistance * right); // inclusive
++                maxZ = toZ - (oldViewDistance * up); // exclusive
++                minZ = fromZ - (oldViewDistance * up); // inclusive
++
++                for (int currX = minX; currX != maxX; currX += right) {
++                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
++                        this.removePlayerFrom(player, currX, currZ);
++                    }
++                }
++            }
++        } else {
++            // different view distance
++            // for now :)
++            this.removePlayer(player, oldPosition, oldViewDistance);
++            this.addNewPlayer(player, newPosition, newViewDistance);
++        }
++    }
++
++    private void removePlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) {
++        final int x = position.getX();
++        final int z = position.getZ();
++
++        for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
++            for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
++                this.removePlayerFrom(player, x + xoff, z + zoff);
++            }
++        }
++    }
++
++    private void addNewPlayer(final EntityPlayer player, final SectionPosition position, final int viewDistance) {
++        final int x = position.getX();
++        final int z = position.getZ();
++
++        for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
++            for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
++                this.addPlayerTo(player, x + xoff, z + zoff);
++            }
++        }
++    }
++}
+diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.util;
++
++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
++import java.lang.ref.WeakReference;
++import java.util.Iterator;
++
++/** @author Spottedleaf */
++public class PooledHashSets<E> {
++
++    // we really want to avoid that equals() check as much as possible...
++    protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f);
++
++    protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> current) {
++        if (current.referenceCount == 0) {
++            throw new IllegalStateException("Cannot decrement reference count for " + current);
++        }
++        if (current.referenceCount == -1 || --current.referenceCount > 0) {
++            return;
++        }
++
++        this.mapPool.remove(current);
++        return;
++    }
++
++    public PooledObjectLinkedOpenHashSet<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
++        final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
++
++        if (cached != null) {
++            if (cached.referenceCount != -1) {
++                ++cached.referenceCount;
++            }
++
++            decrementReferenceCount(current);
++
++            return cached;
++        }
++
++        if (!current.add(object)) {
++            return current;
++        }
++
++        // we use get/put since we use a different key on put
++        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
++
++        if (ret == null) {
++            ret = new PooledObjectLinkedOpenHashSet<>(current);
++            current.remove(object);
++            this.mapPool.put(ret, ret);
++            ret.referenceCount = 1;
++        } else {
++            if (ret.referenceCount != -1) {
++                ++ret.referenceCount;
++            }
++            current.remove(object);
++        }
++
++        current.updateAddCache(object, ret);
++
++        decrementReferenceCount(current);
++        return ret;
++    }
++
++    // rets null if current.size() == 1
++    public PooledObjectLinkedOpenHashSet<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
++        if (current.set.size() == 1) {
++            decrementReferenceCount(current);
++            return null;
++        }
++
++        final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
++
++        if (cached != null) {
++            if (cached.referenceCount != -1) {
++                ++cached.referenceCount;
++            }
++
++            decrementReferenceCount(current);
++
++            return cached;
++        }
++
++        if (!current.remove(object)) {
++            return current;
++        }
++
++        // we use get/put since we use a different key on put
++        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
++
++        if (ret == null) {
++            ret = new PooledObjectLinkedOpenHashSet<>(current);
++            current.add(object);
++            this.mapPool.put(ret, ret);
++            ret.referenceCount = 1;
++        } else {
++            if (ret.referenceCount != -1) {
++                ++ret.referenceCount;
++            }
++            current.add(object);
++        }
++
++        current.updateRemoveCache(object, ret);
++
++        decrementReferenceCount(current);
++        return ret;
++    }
++
++    public static final class PooledObjectLinkedOpenHashSet<E> implements Iterable<E> {
++
++        private static final WeakReference NULL_REFERENCE = new WeakReference(null);
++
++        final ObjectLinkedOpenHashSet<E> set;
++        int referenceCount; // -1 if special
++        int hash; // optimize hashcode
++
++        // add cache
++        WeakReference<E> lastAddObject = NULL_REFERENCE;
++        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
++
++        // remove cache
++        WeakReference<E> lastRemoveObject = NULL_REFERENCE;
++        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
++
++        public PooledObjectLinkedOpenHashSet() {
++            this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f);
++        }
++
++        public PooledObjectLinkedOpenHashSet(final E single) {
++            this();
++            this.referenceCount = -1;
++            this.add(single);
++        }
++
++        public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> other) {
++            this.set = other.set.clone();
++            this.hash = other.hash;
++        }
++
++        // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
++        // generated by https://github.com/skeeto/hash-prospector
++        static int hash0(int x) {
++            x *= 0x36935555;
++            x ^= x >>> 16;
++            return x;
++        }
++
++        public PooledObjectLinkedOpenHashSet<E> getAddCache(final E element) {
++            final E currentAdd = this.lastAddObject.get();
++
++            if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
++                return null;
++            }
++
++            final PooledObjectLinkedOpenHashSet<E> map = this.lastAddMap.get();
++            if (map == null || map.referenceCount == 0) {
++                // we need to ret null if ref count is zero as calling code will assume the map is in use
++                return null;
++            }
++
++            return map;
++        }
++
++        public PooledObjectLinkedOpenHashSet<E> getRemoveCache(final E element) {
++            final E currentRemove = this.lastRemoveObject.get();
++
++            if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
++                return null;
++            }
++
++            final PooledObjectLinkedOpenHashSet<E> map = this.lastRemoveMap.get();
++            if (map == null || map.referenceCount == 0) {
++                // we need to ret null if ref count is zero as calling code will assume the map is in use
++                return null;
++            }
++
++            return map;
++        }
++
++        public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
++            this.lastAddObject = new WeakReference<>(element);
++            this.lastAddMap = new WeakReference<>(map);
++        }
++
++        public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
++            this.lastRemoveObject = new WeakReference<>(element);
++            this.lastRemoveMap = new WeakReference<>(map);
++        }
++
++        boolean add(final E element) {
++            boolean added =  this.set.add(element);
++
++            if (added) {
++                this.hash += hash0(element.hashCode());
++            }
++
++            return added;
++        }
++
++        boolean remove(Object element) {
++            boolean removed = this.set.remove(element);
++
++            if (removed) {
++                this.hash -= hash0(element.hashCode());
++            }
++
++            return removed;
++        }
++
++        @Override
++        public Iterator<E> iterator() {
++            return this.set.iterator();
++        }
++
++        @Override
++        public int hashCode() {
++            return this.hash;
++        }
++
++        @Override
++        public boolean equals(final Object other) {
++            if (!(other instanceof PooledObjectLinkedOpenHashSet)) {
++                return false;
++            }
++            if (this.referenceCount == 0) {
++                return other == this;
++            } else {
++                if (other == this) {
++                    // Unfortunately we are never equal to our own instance while in use!
++                    return false;
++                }
++                return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set);
++            }
++        }
++
++        @Override
++        public String toString() {
++            return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " +
++                this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString();
++        }
++    }
++}
+diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
+             this.world.getMethodProfiler().enter("naturalSpawnCount");
+             this.world.timings.countNaturalMobs.startTiming(); // Paper - timings
+             int l = this.chunkMapDistance.b();
+-            SpawnerCreature.d spawnercreature_d = SpawnerCreature.a(l, this.world.z(), this::a);
++            // Paper start - per player mob spawning
++            SpawnerCreature.d spawnercreature_d; // moved down
++            if (this.playerChunkMap.playerMobDistanceMap != null) {
++                // update distance map
++                this.world.timings.playerMobDistanceMapUpdate.startTiming();
++                this.playerChunkMap.playerMobDistanceMap.update(this.world.players, this.playerChunkMap.viewDistance);
++                this.world.timings.playerMobDistanceMapUpdate.stopTiming();
++                // re-set mob counts
++                for (EntityPlayer player : this.world.players) {
++                    Arrays.fill(player.mobCounts, 0);
++                }
++                spawnercreature_d = SpawnerCreature.countMobs(l, this.world.z(), this::a, true);
++            } else {
++                spawnercreature_d = SpawnerCreature.countMobs(l, this.world.z(), this::a, false);
++            }
++            // Paper end
+             this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings
+ 
+             this.p = spawnercreature_d;
+diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/EntityPlayer.java
+@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+     public boolean queueHealthUpdatePacket = false;
+     public net.minecraft.server.PacketPlayOutUpdateHealth queuedHealthUpdatePacket;
+     // Paper end
++    // Paper start - mob spawning rework
++    public static final int ENUMCREATURETYPE_TOTAL_ENUMS = EnumCreatureType.values().length;
++    public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper
++    public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> cachedSingleMobDistanceMap;
++    // Paper end
+ 
+     // CraftBukkit start
+     public String displayName;
+@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+         this.displayName = this.getName();
+         this.canPickUpLoot = true;
+         this.maxHealthCache = this.getMaxHealth();
++        this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
+     }
+ 
+     // Yes, this doesn't match Vanilla, but it's the best we can do for now.
+@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+ 
+     }
+ 
++    public final SectionPosition getPlayerMapSection() { return this.N(); } // Paper - OBFHELPER
+     public SectionPosition N() {
+         return this.cq;
+     }
+diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/EntityTypes.java
++++ b/src/main/java/net/minecraft/server/EntityTypes.java
+@@ -0,0 +0,0 @@ public class EntityTypes<T extends Entity> {
+         return this.bk;
+     }
+ 
++    public final EnumCreatureType getEnumCreatureType() { return this.e(); } // Paper - OBFHELPER
+     public EnumCreatureType e() {
+         return this.bf;
+     }
+diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
+@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+     public final Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
+     private final Long2ByteMap z;
+     private final Queue<Runnable> A; private final Queue<Runnable> getUnloadQueueTasks() { return this.A; } // Paper - OBFHELPER
+-    private int viewDistance;
++    int viewDistance; // Paper - private -> package private
++    public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper
+ 
+     // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
+     public final CallbackExecutor callbackExecutor = new CallbackExecutor();
+@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+         this.l = supplier;
+         this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper
+         this.setViewDistance(i);
++        this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
++    }
++
++    public void updatePlayerMobTypeMap(Entity entity) {
++        if (!this.world.paperConfig.perPlayerMobSpawns) {
++            return;
++        }
++        int chunkX = (int)Math.floor(entity.locX()) >> 4;
++        int chunkZ = (int)Math.floor(entity.locZ()) >> 4;
++        int index = entity.getEntityType().getEnumCreatureType().ordinal();
++
++        for (EntityPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) {
++            ++player.mobCounts[index];
++        }
++    }
++
++    public int getMobCountNear(EntityPlayer entityPlayer, EnumCreatureType enumCreatureType) {
++        return entityPlayer.mobCounts[enumCreatureType.ordinal()];
+     }
+ 
+     private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) {
+diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
++++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+     });
+ 
+     public static SpawnerCreature.d a(int i, Iterable<Entity> iterable, SpawnerCreature.b spawnercreature_b) {
++        // Paper start - add countMobs parameter
++        return countMobs(i, iterable, spawnercreature_b, false);
++    }
++    public static SpawnerCreature.d countMobs(int i, Iterable<Entity> iterable, SpawnerCreature.b spawnercreature_b, boolean countMobs) {
++        // Paper end - add countMobs parameter
+         SpawnerCreatureProbabilities spawnercreatureprobabilities = new SpawnerCreatureProbabilities();
+         Object2IntOpenHashMap<EnumCreatureType> object2intopenhashmap = new Object2IntOpenHashMap();
+         Iterator iterator = iterable.iterator();
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+                     }
+ 
+                     object2intopenhashmap.addTo(enumcreaturetype, 1);
++                    // Paper start
++                    if (countMobs) {
++                        ((WorldServer)chunk.world).getChunkProvider().playerChunkMap.updatePlayerMobTypeMap(entity);
++                    }
++                    // Paper end
+                 });
+             }
+         }
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+                 continue;
+             }
+ 
++            // Paper start - only allow spawns upto the limit per chunk and update count afterwards
++            int currEntityCount = spawnercreature_d.getEntityCountsByType().getInt(enumcreaturetype);
++            int k1 = limit * spawnercreature_d.getSpawnerChunks() / SpawnerCreature.b;
++            int difference = k1 - currEntityCount;
++
++            if (worldserver.paperConfig.perPlayerMobSpawns) {
++                int minDiff = Integer.MAX_VALUE;
++                for (EntityPlayer entityplayer : worldserver.getChunkProvider().playerChunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) {
++                    minDiff = Math.min(limit - worldserver.getChunkProvider().playerChunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff);
++                }
++                difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
++            }
++            // Paper end
++
++            if (difference > 0) { // Paper
+             if ((flag || !enumcreaturetype.d()) && (flag1 || enumcreaturetype.d()) && (flag2 || !enumcreaturetype.e()) && spawnercreature_d.a(enumcreaturetype, limit)) {
+                 // CraftBukkit end
+-                a(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> {
++                int spawnCount = spawnMobs(enumcreaturetype, worldserver, chunk, (entitytypes, blockposition, ichunkaccess) -> {
+                     return spawnercreature_d.a(entitytypes, blockposition, ichunkaccess);
+                 }, (entityinsentient, ichunkaccess) -> {
+                     spawnercreature_d.a(entityinsentient, ichunkaccess);
++                },
++                limit, worldserver.paperConfig.perPlayerMobSpawns ? worldserver.getChunkProvider().playerChunkMap::updatePlayerMobTypeMap : null);
++                spawnercreature_d.getEntityCountsByType().mergeInt(enumcreaturetype, 0, (keyInMap, valueInMap) -> {
++                    return Integer.valueOf(spawnCount + valueInMap.intValue());
+                 });
++                } // Paper
+             }
+         }
+ 
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+     }
+ 
+     public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) {
++        // Paper start - add parameters and int ret type
++        spawnMobs(enumcreaturetype, worldserver, chunk, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null);
++    }
++    public static int spawnMobs(EnumCreatureType enumcreaturetype, WorldServer worldserver, Chunk chunk, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
++        // Paper end - add parameters and int ret type
+         BlockPosition blockposition = getRandomPosition(worldserver, chunk);
+ 
+         if (blockposition.getY() >= 1) {
+-            a(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a);
++            return spawnMobsInternal(enumcreaturetype, worldserver, (IChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity);
+         }
++        return 0; // Paper
+     }
+ 
+     public static void a(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a) {
++        // Paper start - add maxSpawns parameter and return spawned mobs
++        spawnMobsInternal(enumcreaturetype, worldserver, ichunkaccess, blockposition, spawnercreature_c, spawnercreature_a, Integer.MAX_VALUE, null);
++    }
++    public static int spawnMobsInternal(EnumCreatureType enumcreaturetype, WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition blockposition, SpawnerCreature.c spawnercreature_c, SpawnerCreature.a spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
++        // Paper end - add maxSpawns parameter and return spawned mobs
+         StructureManager structuremanager = worldserver.getStructureManager();
+         ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator();
+         int i = blockposition.getY();
+         IBlockData iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn
++        int j = 0; // Paper - moved up
+ 
+         if (iblockdata != null && !iblockdata.isOccluding(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn
+             BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
+-            int j = 0;
++            // Paper - moved up
+             int k = 0;
+ 
+             while (k < 3) {
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+                                     // Paper start
+                                     Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomebase_biomemeta, blockposition_mutableblockposition, d2);
+                                     if (doSpawning == null) {
+-                                        return;
++                                        return j; // Paper
+                                     }
+                                     if (doSpawning.booleanValue() && spawnercreature_c.test(biomebase_biomemeta.c, blockposition_mutableblockposition, ichunkaccess)) { // Paper end
+                                         EntityInsentient entityinsentient = a(worldserver, biomebase_biomemeta.c);
+ 
+                                         if (entityinsentient == null) {
+-                                            return;
++                                            return j; // Paper
+                                         }
+ 
+                                         entityinsentient.setPositionRotation(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F);
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+                                             groupdataentity = entityinsentient.prepare(worldserver, worldserver.getDamageScaler(entityinsentient.getChunkCoordinates()), EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null);
+                                             // CraftBukkit start
+                                             if (worldserver.addEntity(entityinsentient, SpawnReason.NATURAL)) {
+-                                                ++j;
++                                                ++j; // Paper - force diff on name change - we expect this to be the total amount spawned
+                                                 ++k1;
+                                                 spawnercreature_a.run(entityinsentient, ichunkaccess);
++                                                // Paper start
++                                                if (trackEntity != null) {
++                                                    trackEntity.accept(entityinsentient);
++                                                }
++                                                // Paper end
+                                             }
+                                             // CraftBukkit end
+-                                            if (j >= entityinsentient.getMaxSpawnGroup()) {
+-                                                return;
++                                            if (j >= entityinsentient.getMaxSpawnGroup() || j >= maxSpawns) { // Paper
++                                                return j; // Paper
+                                             }
+ 
+                                             if (entityinsentient.c(k1)) {
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+             }
+ 
+         }
++        return j; // Paper
+     }
+ 
+     private static boolean a(WorldServer worldserver, IChunkAccess ichunkaccess, BlockPosition.MutableBlockPosition blockposition_mutableblockposition, double d0) {
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+ 
+     public static class d {
+ 
+-        private final int a;
+-        private final Object2IntOpenHashMap<EnumCreatureType> b;
++        private final int a; final int getSpawnerChunks() { return this.a; } // Paper - OBFHELPER
++        private final Object2IntOpenHashMap<EnumCreatureType> b; final Object2IntMap<EnumCreatureType> getEntityCountsByType() { return this.b; } // Paper - OBFHELPER
+         private final SpawnerCreatureProbabilities c;
+         private final Object2IntMap<EnumCreatureType> d;
+         @Nullable
+@@ -0,0 +0,0 @@ public final class SpawnerCreature {
+ 
+         // CraftBukkit start
+         private boolean a(EnumCreatureType enumcreaturetype, int limit) {
+-            int i = limit * this.a / SpawnerCreature.b;
++            int i = limit * this.a / SpawnerCreature.b; // Paper - diff on change, needed in the spawn method
+             // CraftBukkit end
+ 
+             return this.b.getInt(enumcreaturetype) < i;

From ce8c91600bea4095248629ec1efe8deea9a8004b Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 22:12:11 -0700
Subject: [PATCH 09/10] Updated Upstream (Bukkit/CraftBukkit/Spigot)

Upstream has released updates that appears to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
3284612a SPIGOT-5853: Add DragonBattle#generateEndPortal()
e4db04ae SPIGOT-5841: New map colours broken

CraftBukkit Changes:
d4243510 SPIGOT-5853: DragonBattle#getEndPortalLocation() throws NPE on new world
1601ec31 SPIGOT-5845: ChatColor.RESET does not work in ItemMeta to reset italics
4d92db6f CraftChatMessageTest does not need AbstractTestingBase
71045d3d SPIGOT-5828: Unlock worlds on unload
dbc347b9 SPIGOT-5841: New map colours broken
14053c70 SPIGOT-5847: BlockFadeEvent cannot be triggered asynchronously from another thread

Spigot Changes:
6f4ff1b6 SPIGOT-5851: ChatColor (HEX) doesn't appear correctly in the ActionBar
d94a518a SPIGOT-5848: PlayerSpawnLocationEvent throws NPE when setting a location of another world
---
 .../Add-PlayerInitialSpawnEvent.patch          |  5 +++--
 ...-to-disable-ender-dragon-legacy-check.patch | 18 ------------------
 ...-t-fire-BlockFade-on-worldgen-threads.patch |  4 ++--
 work/Bukkit                                    |  2 +-
 work/CraftBukkit                               |  2 +-
 work/Spigot                                    |  2 +-
 6 files changed, 8 insertions(+), 25 deletions(-)

diff --git a/Spigot-Server-Patches/Add-PlayerInitialSpawnEvent.patch b/Spigot-Server-Patches/Add-PlayerInitialSpawnEvent.patch
index 0518e81866..df948df91f 100644
--- a/Spigot-Server-Patches/Add-PlayerInitialSpawnEvent.patch
+++ b/Spigot-Server-Patches/Add-PlayerInitialSpawnEvent.patch
@@ -21,9 +21,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Bukkit.getPluginManager().callEvent(ev);
  
          Location loc = ev.getSpawnLocation();
-         worldserver = ((CraftWorld) loc.getWorld()).getHandle();
+@@ -0,0 +0,0 @@ public abstract class PlayerList {
  
-         entityplayer.spawnIn(worldserver);
+         entityplayer.spawnIn(worldserver1);
+         entityplayer.playerInteractManager.a((WorldServer) entityplayer.world);
 -        entityplayer.setPosition(loc.getX(), loc.getY(), loc.getZ());
 +        entityplayer.setPositionRaw(loc.getX(), loc.getY(), loc.getZ()); // Paper - set raw so we aren't fully joined to the world (not added to chunk or world)
          entityplayer.setYawPitch(loc.getYaw(), loc.getPitch());
diff --git a/Spigot-Server-Patches/Add-config-to-disable-ender-dragon-legacy-check.patch b/Spigot-Server-Patches/Add-config-to-disable-ender-dragon-legacy-check.patch
index c6a4124285..db52112c64 100644
--- a/Spigot-Server-Patches/Add-config-to-disable-ender-dragon-legacy-check.patch
+++ b/Spigot-Server-Patches/Add-config-to-disable-ender-dragon-legacy-check.patch
@@ -46,21 +46,3 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.world = worldserver;
          if (nbttagcompound.hasKeyOfType("DragonKilled", 99)) {
              if (nbttagcompound.b("Dragon")) {
-@@ -0,0 +0,0 @@ public class EnderDragonBattle {
- 
-     private void a(BlockPosition blockposition) {
-         this.world.triggerEffect(3000, blockposition, 0);
--        WorldGenerator.END_GATEWAY.b((WorldGenFeatureConfiguration) WorldGenEndGatewayConfiguration.a()).a(this.world, this.world.getStructureManager(), this.world.getChunkProvider().getChunkGenerator(), new Random(), blockposition);
-+        WorldGenerator.END_GATEWAY.b(WorldGenEndGatewayConfiguration.a()).a(this.world, this.world.getStructureManager(), this.world.getChunkProvider().getChunkGenerator(), new Random(), blockposition); // Paper - decompile error
-     }
- 
-     private void a(boolean flag) {
-@@ -0,0 +0,0 @@ public class EnderDragonBattle {
-             }
-         }
- 
--        worldgenendtrophy.b((WorldGenFeatureConfiguration) WorldGenFeatureConfiguration.k).a(this.world, this.world.getStructureManager(), this.world.getChunkProvider().getChunkGenerator(), new Random(), this.exitPortalLocation);
-+        worldgenendtrophy.b(WorldGenFeatureConfiguration.k).a(this.world, this.world.getStructureManager(), this.world.getChunkProvider().getChunkGenerator(), new Random(), this.exitPortalLocation); // Paper - decompile error
-     }
- 
-     private EntityEnderDragon o() {
diff --git a/Spigot-Server-Patches/Don-t-fire-BlockFade-on-worldgen-threads.patch b/Spigot-Server-Patches/Don-t-fire-BlockFade-on-worldgen-threads.patch
index da24b21125..ea6b460cec 100644
--- a/Spigot-Server-Patches/Don-t-fire-BlockFade-on-worldgen-threads.patch
+++ b/Spigot-Server-Patches/Don-t-fire-BlockFade-on-worldgen-threads.patch
@@ -15,8 +15,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          // CraftBukkit start
 +        if (!(generatoraccess instanceof WorldServer)) return this.canPlace(iblockdata, generatoraccess, blockposition) ? (IBlockData) this.a(generatoraccess, blockposition, (Integer) iblockdata.get(BlockFire.AGE)) : Blocks.AIR.getBlockData(); // Paper - don't fire events in world generation
          if (!this.canPlace(iblockdata, generatoraccess, blockposition)) {
-             CraftBlockState blockState = CraftBlockState.getBlockState(generatoraccess, blockposition);
-             blockState.setData(Blocks.AIR.getBlockData());
+             // Suppress during worldgen
+             if (!(generatoraccess instanceof World)) {
 @@ -0,0 +0,0 @@ public class BlockFire extends BlockFireAbstract {
                  return blockState.getHandle();
              }
diff --git a/work/Bukkit b/work/Bukkit
index edc7a378c9..3284612a10 160000
--- a/work/Bukkit
+++ b/work/Bukkit
@@ -1 +1 @@
-Subproject commit edc7a378c9258ef533f90de135e250ca91b1ae02
+Subproject commit 3284612a10e704b4658aa087213a3fa9670b6926
diff --git a/work/CraftBukkit b/work/CraftBukkit
index 4ff609e60c..d424351010 160000
--- a/work/CraftBukkit
+++ b/work/CraftBukkit
@@ -1 +1 @@
-Subproject commit 4ff609e60c191f107682681b5e3a8262f8a4c844
+Subproject commit d4243510102530684b860dbe927311bf47c488db
diff --git a/work/Spigot b/work/Spigot
index 16d78990fe..6f4ff1b691 160000
--- a/work/Spigot
+++ b/work/Spigot
@@ -1 +1 @@
-Subproject commit 16d78990fe27633e0ec129216f96d3b50f770025
+Subproject commit 6f4ff1b691828e538ae534eac66e80a1bf2c1adf

From 9e1ccb01be0361390a4f0b869c28c0313466ba11 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 26 Jun 2020 22:37:38 -0700
Subject: [PATCH 10/10] Hide sync chunk writes behind flag

---
 .../Hide-sync-chunk-writes-behind-flag.patch  | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 Spigot-Server-Patches/Hide-sync-chunk-writes-behind-flag.patch

diff --git a/Spigot-Server-Patches/Hide-sync-chunk-writes-behind-flag.patch b/Spigot-Server-Patches/Hide-sync-chunk-writes-behind-flag.patch
new file mode 100644
index 0000000000..4a0b0832a6
--- /dev/null
+++ b/Spigot-Server-Patches/Hide-sync-chunk-writes-behind-flag.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
+Date: Fri, 26 Jun 2020 22:35:08 -0700
+Subject: [PATCH] Hide sync chunk writes behind flag
+
+Syncing writes on each write call has terrible performance
+on harddrives.
+
+-DPaper.enable-sync-chunk-writes=true to enable
+
+diff --git a/src/main/java/net/minecraft/server/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/DedicatedServerProperties.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/DedicatedServerProperties.java
++++ b/src/main/java/net/minecraft/server/DedicatedServerProperties.java
+@@ -0,0 +0,0 @@ public class DedicatedServerProperties extends PropertyManager<DedicatedServerPr
+         this.maxWorldSize = this.a("max-world-size", (integer) -> {
+             return MathHelper.clamp(integer, 1, 29999984);
+         }, 29999984);
+-        this.syncChunkWrites = this.getBoolean("sync-chunk-writes", true);
++        this.syncChunkWrites = this.getBoolean("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag
+         this.enableJmxMonitoring = this.getBoolean("enable-jmx-monitoring", false);
+         this.enableStatus = this.getBoolean("enable-status", true);
+         this.entityBroadcastRangePercentage = this.a("entity-broadcast-range-percentage", (integer) -> {