diff --git a/Spigot-Server-Patches/0003-MC-Dev-fixes.patch b/Spigot-Server-Patches/0003-MC-Dev-fixes.patch
index bf06b080b..34bd17b8b 100644
--- a/Spigot-Server-Patches/0003-MC-Dev-fixes.patch
+++ b/Spigot-Server-Patches/0003-MC-Dev-fixes.patch
@@ -49,6 +49,19 @@ index a3afe60b0d85cf90bf7a170dc0a0b61a796381a7..85f799a713db0c822d46b689010f9f6b
          } else {
              System.arraycopy(this.b, 0, au, 0, this.c);
              if (au.length > this.c) {
+diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+index 37006ec1bbb8fa285257edacdf337591595852a2..35eb3a5a61145e94d5b0c77c0eb13bfa46fac23b 100644
+--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
++++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+@@ -53,7 +53,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
+                 villageplace.a(this.a.c(), (blockposition1) -> {
+                     return blockposition1.equals(blockposition);
+                 }, blockposition, 1);
+-                entitycreature.getBehaviorController().setMemory(this.b, (Object) GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), blockposition));
++                entitycreature.getBehaviorController().setMemory(this.b, GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), blockposition));
+                 PacketDebug.c(worldserver, blockposition);
+             });
+         } else if (this.f < 5) {
 diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java
 index 960dce23072bbb5fad36760677f0fe2efb661552..253890e53702f9ba1c6628cc860a4ca10756626a 100644
 --- a/src/main/java/net/minecraft/server/BiomeBase.java
diff --git a/Spigot-Server-Patches/0004-MC-Utils.patch b/Spigot-Server-Patches/0004-MC-Utils.patch
index 6ebbdd83d..64fe07043 100644
--- a/Spigot-Server-Patches/0004-MC-Utils.patch
+++ b/Spigot-Server-Patches/0004-MC-Utils.patch
@@ -2283,6 +2283,18 @@ index 4f60b931a143ebf70a8469913ec445ff13da4d8d..3e90b57b6fd5dcb6cb1325861306e2ff
      public double a() {
          double d0 = this.b();
          double d1 = this.c();
+diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java
+index a3b5793e4824718c8bf3d0a4f963de0ca94a738e..3f09c24e1cd1bba2809b70b1fa6e89773537d834 100644
+--- a/src/main/java/net/minecraft/server/BaseBlockPosition.java
++++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java
+@@ -80,6 +80,7 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
+         return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0;
+     }
+ 
++    public final double distanceSquared(BaseBlockPosition baseblockposition) { return m(baseblockposition); } // Paper - OBFHELPER
+     public double m(BaseBlockPosition baseblockposition) {
+         return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true);
+     }
 diff --git a/src/main/java/net/minecraft/server/BlockAccessAir.java b/src/main/java/net/minecraft/server/BlockAccessAir.java
 index eff6ebcd30b538cbaedaa031a46a59ea956253ba..30cbfc8eac20910aa55951e3dce63862f5a43c37 100644
 --- a/src/main/java/net/minecraft/server/BlockAccessAir.java
diff --git a/Spigot-Server-Patches/0251-Improve-BlockPosition-inlining.patch b/Spigot-Server-Patches/0251-Improve-BlockPosition-inlining.patch
index 2d668ce57..366f75128 100644
--- a/Spigot-Server-Patches/0251-Improve-BlockPosition-inlining.patch
+++ b/Spigot-Server-Patches/0251-Improve-BlockPosition-inlining.patch
@@ -21,7 +21,7 @@ This is based upon conclusions drawn from inspecting the assenmbly generated byt
 They had 'callq' (invoke) instead of 'mov' (get from memory) instructions.
 
 diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java
-index 71089442c189336fc0061852a661581784a64013..8f47b6c9e70d10c444ab328dc3bf3a46499b14d5 100644
+index 7b05bb9edcd059a134cef12cc9fea570217bc601..a0450a7ddf21659c5636b3f298e6bf4f0a93fc4d 100644
 --- a/src/main/java/net/minecraft/server/BaseBlockPosition.java
 +++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java
 @@ -7,32 +7,30 @@ import javax.annotation.concurrent.Immutable;
@@ -126,7 +126,7 @@ index 71089442c189336fc0061852a661581784a64013..8f47b6c9e70d10c444ab328dc3bf3a46
      }
  
      public boolean a(IPosition iposition, double d0) {
-@@ -106,9 +107,9 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
+@@ -107,9 +108,9 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
      }
  
      public int n(BaseBlockPosition baseblockposition) {
diff --git a/Spigot-Server-Patches/0533-Optimize-Villagers.patch b/Spigot-Server-Patches/0533-Optimize-Villagers.patch
new file mode 100644
index 000000000..d1e4ed9fe
--- /dev/null
+++ b/Spigot-Server-Patches/0533-Optimize-Villagers.patch
@@ -0,0 +1,272 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
+Date: Tue, 26 May 2020 21:32:05 -0400
+Subject: [PATCH] Optimize Villagers
+
+This change reimplements the entire BehaviorFindPosition method to
+get rid of all of the streams, and implement the logic in a more sane way.
+
+We keep vanilla behavior 100% the same with this change, just wrote more
+optimal, as we can abort iterating POI's as soon as we find a match....
+
+One slight change is that Minecraft adds a random delay before a POI is
+attempted again. I've increased the amount of that delay based on the distance
+to said POI, so farther POI's will not be attempted as often.
+
+Additionally, we spiral out, so we favor local POI's before we ever favor farther POI's.
+
+We also try to pathfind 1 POI at a time instead of collecting multiple POI's then tossing them
+all to the pathfinder, so that once we get a match we can return before even looking at other
+POI's.
+
+This benefits us in that ideally, a villager will constantly find the near POI's and
+not even try to pathfind to the farther POI. Trying to pathfind to distant POI's is
+what causes significant lag.
+
+Other improvements here is to stop spamming the POI manager with empty nullables.
+Vanilla used them to represent if they needed to load POI data off disk or not.
+
+Well, we load POI data async on chunk load, so we have it, and we surely do not ever
+want to load POI data sync either for unloaded chunks!
+
+So this massively reduces object count in the POI hashmaps, resulting in less hash collions,
+and also less memory use.
+
+Additionally, unemployed villagers were using significant time due to major ineffeciency in
+the code rebuilding data that is static every single invocation for every POI type...
+
+So we cache that and only rebuild it if professions change, which should be never unless
+a plugin manipulates and adds custom professions, which it will handle by rebuilding.
+
+diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+index 35eb3a5a61145e94d5b0c77c0eb13bfa46fac23b..be85c20bdc7dc10e6389d4ad8738fd5866b83ebd 100644
+--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
++++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+@@ -29,8 +29,54 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
+ 
+     protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) {
+         this.f = 0;
+-        this.d = worldserver.getTime() + (long) worldserver.getRandom().nextInt(20);
++        this.d = worldserver.getTime() + (long) java.util.concurrent.ThreadLocalRandom.current().nextInt(20); // Paper
+         VillagePlace villageplace = worldserver.B();
++
++        // Paper start - replace implementation completely
++        BlockPosition blockposition2 = new BlockPosition(entitycreature);
++        int dist = 48;
++        int requiredDist = dist * dist;
++        int cdist = Math.floorDiv(dist, 16);
++        Predicate<VillagePlaceType> predicate = this.a.c();
++        int maxPoiAttempts = 4;
++        int poiAttempts = 0;
++        OUT:
++        for (ChunkCoordIntPair chunkcoordintpair : MCUtil.getSpiralOutChunks(blockposition2, cdist)) {
++            for (int i1 = 0; i1 < 16; i1++) {
++                java.util.Optional<VillagePlaceSection> section = villageplace.getSection(SectionPosition.a(chunkcoordintpair, i1).v());
++                if (section == null || !section.isPresent()) continue;
++                for (java.util.Map.Entry<VillagePlaceType, java.util.Set<VillagePlaceRecord>> e : section.get().getRecords().entrySet()) {
++                    if (!predicate.test(e.getKey())) continue;
++                    for (VillagePlaceRecord record : e.getValue()) {
++                        if (!record.hasVacancy()) continue;
++
++                        BlockPosition pos = record.getPosition();
++                        long key = pos.asLong();
++                        if (this.e.containsKey(key)) {
++                            continue;
++                        }
++                        double poiDist = pos.distanceSquared(blockposition2);
++                        if (poiDist <= (double) requiredDist) {
++                            this.e.put(key, (long) (this.d + Math.sqrt(poiDist) * 4)); // use dist instead of 40 to blacklist longer if farther distance
++                            ++poiAttempts;
++                            PathEntity pathentity = entitycreature.getNavigation().a(com.google.common.collect.ImmutableSet.of(pos), 8, false, this.a.d());
++
++                            if (poiAttempts <= maxPoiAttempts && pathentity != null && pathentity.h()) {
++                                record.decreaseVacancy();
++                                GlobalPos globalPos = GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), pos);
++                                entitycreature.getBehaviorController().setMemory(this.b, globalPos);
++                                break OUT;
++                            }
++                            if (poiAttempts > maxPoiAttempts) {
++                                this.e.long2LongEntrySet().removeIf((entry) -> entry.getLongValue() < this.d);
++                                break OUT;
++                            }
++                        }
++                    }
++                }
++            }
++        }
++        /*
+         Predicate<BlockPosition> predicate = (blockposition) -> {
+             long j = blockposition.asLong();
+ 
+@@ -61,6 +107,6 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
+                 return entry.getLongValue() < this.d;
+             });
+         }
+-
++        */ // Paper end
+     }
+ }
+diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java
+index a6d8ef5eb44f3f851a3a1be4032ca21ab1d7f2b2..c3e8a0145d63843736d2060f978cdf38df359563 100644
+--- a/src/main/java/net/minecraft/server/RegionFileSection.java
++++ b/src/main/java/net/minecraft/server/RegionFileSection.java
+@@ -51,29 +51,15 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
+ 
+     @Nullable
+     protected Optional<R> c(long i) {
+-        return (Optional) this.c.get(i);
++        return this.c.getOrDefault(i, Optional.empty()); // Paper
+     }
+ 
++    protected final Optional<R> getSection(long i) { return d(i); } // Paper - OBFHELPER
+     protected Optional<R> d(long i) {
+-        SectionPosition sectionposition = SectionPosition.a(i);
+-
+-        if (this.b(sectionposition)) {
+-            return Optional.empty();
+-        } else {
+-            Optional<R> optional = this.c(i);
+-
+-            if (optional != null) {
+-                return optional;
+-            } else {
+-                this.b(sectionposition.u());
+-                optional = this.c(i);
+-                if (optional == null) {
+-                    throw (IllegalStateException) SystemUtils.c(new IllegalStateException());
+-                } else {
+-                    return optional;
+-                }
+-            }
+-        }
++        // Paper start - replace method - never load POI data sync, we load this in chunk load already, reduce ops
++        // If it's an unloaded chunk, well too bad.
++        return this.c(i);
++        // Paper end
+     }
+ 
+     protected boolean b(SectionPosition sectionposition) {
+@@ -117,7 +103,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
+     private <T> void a(ChunkCoordIntPair chunkcoordintpair, DynamicOps<T> dynamicops, @Nullable T t0) {
+         if (t0 == null) {
+             for (int i = 0; i < 16; ++i) {
+-                this.c.put(SectionPosition.a(chunkcoordintpair, i).v(), Optional.empty());
++                //this.c.put(SectionPosition.a(chunkcoordintpair, i).v(), Optional.empty()); // Paper - NO!!!
+             }
+         } else {
+             Dynamic<T> dynamic = new Dynamic(dynamicops, t0);
+@@ -135,7 +121,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
+                     }, dynamic2);
+                 });
+ 
+-                this.c.put(i1, optional);
++                if (optional.isPresent()) this.c.put(i1, optional); // Paper - NO!!!
+                 optional.ifPresent((minecraftserializable) -> {
+                     this.b(i1);
+                     if (flag) {
+@@ -199,7 +185,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
+         if (optional != null && optional.isPresent()) {
+             this.d.add(i);
+         } else {
+-            RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i));
++            //RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i)); // Paper - hush
+         }
+     }
+ 
+diff --git a/src/main/java/net/minecraft/server/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
+index 1e9d7a3f902eb4571b93bb0e58cba966365f07b8..44535a3e2c3320aac472c5a7ee557fac7bab2530 100644
+--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
++++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
+@@ -32,6 +32,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
+         return dynamicops.createMap(ImmutableMap.of(dynamicops.createString("pos"), this.a.a(dynamicops), dynamicops.createString("type"), dynamicops.createString(IRegistry.POINT_OF_INTEREST_TYPE.getKey(this.b).toString()), dynamicops.createString("free_tickets"), dynamicops.createInt(this.c)));
+     }
+ 
++    protected final boolean decreaseVacancy() { return b(); } // Paper - OBFHELPER
+     protected boolean b() {
+         if (this.c <= 0) {
+             return false;
+@@ -42,6 +43,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
+         }
+     }
+ 
++    protected final boolean increaseVacancy() { return c(); } // Paper - OBFHELPER
+     protected boolean c() {
+         if (this.c >= this.b.b()) {
+             return false;
+@@ -52,14 +54,17 @@ public class VillagePlaceRecord implements MinecraftSerializable {
+         }
+     }
+ 
++    public final boolean hasVacancy() { return d(); } // Paper - OBFHELPER
+     public boolean d() {
+         return this.c > 0;
+     }
+ 
++    public final boolean isOccupied() { return e(); } // Paper - OBFHELPER
+     public boolean e() {
+         return this.c != this.b.b();
+     }
+ 
++    public final BlockPosition getPosition() { return f(); } // Paper
+     public BlockPosition f() {
+         return this.a;
+     }
+diff --git a/src/main/java/net/minecraft/server/VillagePlaceSection.java b/src/main/java/net/minecraft/server/VillagePlaceSection.java
+index 3f2602dbe0995f8d01d4a1428d919405d711a205..436b064c3b277143075386fc9a71027fb5962681 100644
+--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
++++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
+@@ -23,7 +23,7 @@ public class VillagePlaceSection implements MinecraftSerializable {
+ 
+     private static final Logger LOGGER = LogManager.getLogger();
+     private final Short2ObjectMap<VillagePlaceRecord> b = new Short2ObjectOpenHashMap();
+-    private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap();
++    private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap(); public final Map<VillagePlaceType, Set<VillagePlaceRecord>> getRecords() { return c; } // Paper - OBFHELPER
+     private final Runnable d;
+     private boolean e;
+ 
+diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java
+index ab3e054cd2f38756a5d802d4d981022318ab047d..c1f293fc98d3efb4665cfb9036f208b842fc8e36 100644
+--- a/src/main/java/net/minecraft/server/VillagePlaceType.java
++++ b/src/main/java/net/minecraft/server/VillagePlaceType.java
+@@ -12,8 +12,14 @@ import java.util.stream.Stream;
+ 
+ public class VillagePlaceType {
+ 
++    static Set<VillagePlaceType> professionCache; // Paper
+     private static final Predicate<VillagePlaceType> v = (villageplacetype) -> {
+-        return ((Set) IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet())).contains(villageplacetype);
++        // Paper start
++        if (professionCache == null) {
++            professionCache = IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet());
++        }
++        return professionCache.contains(villageplacetype);
++        // Paper end
+     };
+     public static final Predicate<VillagePlaceType> a = (villageplacetype) -> {
+         return true;
+@@ -89,11 +95,11 @@ public class VillagePlaceType {
+     }
+ 
+     private static VillagePlaceType a(String s, Set<IBlockData> set, int i, int j) {
+-        return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, j))));
++        return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, j)))); // Paper - decompile error
+     }
+ 
+     private static VillagePlaceType a(String s, Set<IBlockData> set, int i, Predicate<VillagePlaceType> predicate, int j) {
+-        return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, predicate, j))));
++        return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, predicate, j)))); // Paper - decompile error
+     }
+ 
+     private static VillagePlaceType a(VillagePlaceType villageplacetype) {
+diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java
+index c38296165b33698bc15fe49a2de0d0d19cfb910a..f9d7a16c79a4e3ffe8b6e7ed469236a93892f01d 100644
+--- a/src/main/java/net/minecraft/server/VillagerProfession.java
++++ b/src/main/java/net/minecraft/server/VillagerProfession.java
+@@ -61,6 +61,7 @@ public class VillagerProfession {
+     }
+ 
+     static VillagerProfession a(String s, VillagePlaceType villageplacetype, ImmutableSet<Item> immutableset, ImmutableSet<Block> immutableset1, @Nullable SoundEffect soundeffect) {
++        VillagePlaceType.professionCache = null; // Paper
+         return (VillagerProfession) IRegistry.a((IRegistry) IRegistry.VILLAGER_PROFESSION, new MinecraftKey(s), (Object) (new VillagerProfession(s, villageplacetype, immutableset, immutableset1, soundeffect)));
+     }
+ }