--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -52,6 +_,16 @@
         this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
     }
 
+    // CraftBukkit start - add method to get all entities in chunk
+    public List<Entity> getEntities(ChunkPos chunkPos) {
+        return this.sectionStorage.getExistingSectionsInChunk(chunkPos.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList());
+    }
+
+    public boolean isPending(long pair) {
+        return this.chunkLoadStatuses.get(pair) == ChunkLoadStatus.PENDING;
+    }
+    // CraftBukkit end
+
     void removeSectionIfEmpty(long sectionKey, EntitySection<T> section) {
         if (section.isEmpty()) {
             this.sectionStorage.remove(sectionKey);
@@ -59,6 +_,7 @@
     }
 
     private boolean addEntityUuid(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper
         if (!this.knownUuids.add(entity.getUUID())) {
             LOGGER.warn("UUID of added entity already exists: {}", entity);
             return false;
@@ -72,6 +_,17 @@
     }
 
     private boolean addEntity(T entity, boolean worldGenSpawned) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
+        // Paper start - chunk system hooks
+        // I don't want to know why this is a generic type.
+        Entity entityCasted = (Entity)entity;
+        boolean wasRemoved = entityCasted.isRemoved();
+        boolean screened = ca.spottedleaf.moonrise.common.PlatformHooks.get().screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, worldGenSpawned, true);
+        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
+            // removed by callback
+            return false;
+        }
+        // Paper end - chunk system hooks
         if (!this.addEntityUuid(entity)) {
             return false;
         } else {
@@ -113,19 +_,23 @@
     }
 
     void startTicking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper
         this.callbacks.onTickingStart(entity);
     }
 
     void stopTicking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper
         this.callbacks.onTickingEnd(entity);
     }
 
     void startTracking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper
         this.visibleEntityStorage.add(entity);
         this.callbacks.onTrackingStart(entity);
     }
 
     void stopTracking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper
         this.callbacks.onTrackingEnd(entity);
         this.visibleEntityStorage.remove(entity);
     }
@@ -136,6 +_,7 @@
     }
 
     public void updateChunkStatus(ChunkPos pos, Visibility visibility) {
+        org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper
         long packedChunkPos = pos.toLong();
         if (visibility == Visibility.HIDDEN) {
             this.chunkVisibility.remove(packedChunkPos);
@@ -169,6 +_,7 @@
     }
 
     public void ensureChunkQueuedForLoad(long chunkPosValue) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper
         PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPosValue);
         if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
             this.requestChunkLoad(chunkPosValue);
@@ -176,6 +_,11 @@
     }
 
     private boolean storeChunkSections(long chunkPosValue, Consumer<T> entityAction) {
+        // CraftBukkit start
+        return storeChunkSections(chunkPosValue, entityAction, false);
+    }
+    private boolean storeChunkSections(long chunkPosValue, Consumer<T> entityAction, boolean callEvent) {
+        // CraftBukkit end
         PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPosValue);
         if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
             return false;
@@ -186,6 +_,7 @@
                 .collect(Collectors.toList());
             if (list.isEmpty()) {
                 if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
+                    if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(chunkPosValue), ImmutableList.of()); // CraftBukkit
                     this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), ImmutableList.of()));
                 }
 
@@ -194,6 +_,7 @@
                 this.requestChunkLoad(chunkPosValue);
                 return false;
             } else {
+                if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(chunkPosValue), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
                 this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), list));
                 list.forEach(entityAction);
                 return true;
@@ -202,6 +_,7 @@
     }
 
     private void requestChunkLoad(long chunkPosValue) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper
         this.chunkLoadStatuses.put(chunkPosValue, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
         ChunkPos chunkPos = new ChunkPos(chunkPosValue);
         this.permanentStorage.loadEntities(chunkPos).thenAccept(this.loadingInbox::add).exceptionally(throwable -> {
@@ -211,7 +_,8 @@
     }
 
     private boolean processChunkUnload(long chunkPosValue) {
-        boolean flag = this.storeChunkSections(chunkPosValue, entity -> entity.getPassengersAndSelf().forEach(this::unloadEntity));
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper
+        boolean flag = this.storeChunkSections(chunkPosValue, entity -> entity.getPassengersAndSelf().forEach(this::unloadEntity), true); // CraftBukkit - add boolean for event call
         if (!flag) {
             return false;
         } else {
@@ -221,7 +_,7 @@
     }
 
     private void unloadEntity(EntityAccess entity) {
-        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
+        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); // CraftBukkit - add Bukkit remove cause
         entity.setLevelCallback(EntityInLevelCallback.NULL);
     }
 
@@ -231,14 +_,20 @@
     }
 
     public void processPendingLoads() {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper
         ChunkEntities<T> chunkEntities;
         while ((chunkEntities = this.loadingInbox.poll()) != null) {
             chunkEntities.getEntities().forEach(entity -> this.addEntity((T)entity, true));
             this.chunkLoadStatuses.put(chunkEntities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
+            // CraftBukkit start - call entity load event
+            List<Entity> entities = this.getEntities(chunkEntities.getPos());
+            org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, chunkEntities.getPos(), entities);
+            // CraftBukkit end
         }
     }
 
     public void tick() {
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper
         this.processPendingLoads();
         this.processUnloads();
     }
@@ -256,6 +_,7 @@
     }
 
     public void autoSave() {
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper
         this.getAllChunksToSave().forEach(packedChunkPos -> {
             boolean flag = this.chunkVisibility.get(packedChunkPos) == Visibility.HIDDEN;
             if (flag) {
@@ -267,6 +_,7 @@
     }
 
     public void saveAll() {
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper
         LongSet allChunksToSave = this.getAllChunksToSave();
 
         while (!allChunksToSave.isEmpty()) {
@@ -283,7 +_,13 @@
 
     @Override
     public void close() throws IOException {
-        this.saveAll();
+        // CraftBukkit start
+        this.close(true);
+    }
+
+    public void close(boolean save) throws IOException {
+        if (save) this.saveAll();
+        // CraftBukkit end
         this.permanentStorage.close();
     }
 
@@ -384,6 +_,7 @@
             BlockPos blockPos = this.entity.blockPosition();
             long packedSectionPos = SectionPos.asLong(blockPos);
             if (packedSectionPos != this.currentSectionKey) {
+                org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper
                 Visibility status = this.currentSection.getStatus();
                 if (!this.currentSection.remove(this.entity)) {
                     PersistentEntitySectionManager.LOGGER
@@ -431,6 +_,7 @@
 
         @Override
         public void onRemove(Entity.RemovalReason reason) {
+            org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper
             if (!this.currentSection.remove(this.entity)) {
                 PersistentEntitySectionManager.LOGGER
                     .warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason);
