From 28c8009a16bf679d8f6310d1e7ee4eeb5c37fc4b Mon Sep 17 00:00:00 2001
From: CraftBukkit/Spigot <noreply+git-craftbukkit@papermc.io>
Date: Sat, 2 Nov 2024 18:16:11 +1100
Subject: [PATCH] Entity Activation Range

This feature gives 3 new configurable ranges that if an entity of the matching type is outside of this radius of any player, will tick at 5% of its normal rate.

This will drastically cut down on tick timings for entities that are not in range of a user to actually be "used".
This change can have dramatic impact on gameplay if configured too low. Balance according to your servers desired gameplay.

By: Aikar <aikar@aikar.co>
---
 .../server/level/ServerLevel.java.patch       |  78 +++---
 .../world/entity/AgeableMob.java.patch        |  39 ++-
 .../world/entity/AreaEffectCloud.java.patch   |  36 ++-
 .../minecraft/world/entity/Entity.java.patch  | 201 +++++++------
 .../world/entity/LivingEntity.java.patch      | 187 +++++++------
 .../world/entity/item/ItemEntity.java.patch   |  44 ++-
 .../world/entity/npc/Villager.java.patch      |  37 ++-
 .../projectile/AbstractArrow.java.patch       |  47 +++-
 .../FireworkRocketEntity.java.patch           |  40 ++-
 .../org/bukkit/craftbukkit/SpigotTimings.java |   3 +
 .../java/org/spigotmc/ActivationRange.java    | 263 ++++++++++++++++++
 .../java/org/spigotmc/SpigotWorldConfig.java  |  17 ++
 12 files changed, 730 insertions(+), 262 deletions(-)
 create mode 100644 paper-server/src/main/java/org/spigotmc/ActivationRange.java

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index a6b71ce601..790eea04e1 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -79,7 +79,7 @@
 +    // CraftBukkit start
 +    public final LevelStorageSource.LevelStorageAccess convertable;
 +    public final UUID uuid;
-+
+ 
 +    public LevelChunk getChunkIfLoaded(int x, int z) {
 +        return this.chunkSource.getChunk(x, z, false);
 +    }
@@ -103,7 +103,7 @@
 +        ChunkGenerator chunkgenerator = worlddimension.generator();
 +        // CraftBukkit start
 +        this.serverLevelData.setWorld(this);
- 
++
 +        if (biomeProvider != null) {
 +            BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME));
 +            if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
@@ -230,7 +230,7 @@
  
          if (flag1) {
              this.resetEmptyTime();
-@@ -353,12 +413,14 @@
+@@ -353,12 +413,15 @@
  
          if (flag1 || this.emptyTime++ < 300) {
              gameprofilerfiller.push("entities");
@@ -241,11 +241,12 @@
                  gameprofilerfiller.pop();
              }
  
++            org.spigotmc.ActivationRange.activateEntities(this); // Spigot
 +            this.timings.entityTick.startTiming(); // Spigot
              this.entityTickList.forEach((entity) -> {
                  if (!entity.isRemoved()) {
                      if (!tickratemanager.isEntityFrozen(entity)) {
-@@ -383,6 +445,8 @@
+@@ -383,6 +446,8 @@
                      }
                  }
              });
@@ -254,7 +255,7 @@
              gameprofilerfiller.pop();
              this.tickBlockEntities();
          }
-@@ -429,7 +493,7 @@
+@@ -429,7 +494,7 @@
  
      private void wakeUpAllPlayers() {
          this.sleepStatus.removeAllSleepers();
@@ -263,7 +264,7 @@
              entityplayer.stopSleepInBed(false, false);
          });
      }
-@@ -456,7 +520,7 @@
+@@ -456,7 +521,7 @@
                          entityhorseskeleton.setTrap(true);
                          entityhorseskeleton.setAge(0);
                          entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
@@ -272,7 +273,7 @@
                      }
                  }
  
-@@ -465,7 +529,7 @@
+@@ -465,7 +530,7 @@
                  if (entitylightning != null) {
                      entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
                      entitylightning.setVisualOnly(flag1);
@@ -281,7 +282,7 @@
                  }
              }
          }
-@@ -521,7 +585,7 @@
+@@ -521,7 +586,7 @@
          Biome biomebase = (Biome) this.getBiome(blockposition1).value();
  
          if (biomebase.shouldFreeze(this, blockposition2)) {
@@ -290,7 +291,7 @@
          }
  
          if (this.isRaining()) {
-@@ -537,10 +601,10 @@
+@@ -537,10 +602,10 @@
                          BlockState iblockdata1 = (BlockState) iblockdata.setValue(SnowLayerBlock.LAYERS, j + 1);
  
                          Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1);
@@ -303,7 +304,7 @@
                  }
              }
  
-@@ -701,33 +765,67 @@
+@@ -701,33 +766,67 @@
              this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
          }
  
@@ -379,15 +380,22 @@
      }
  
      public void resetEmptyTime() {
-@@ -754,6 +852,7 @@
+@@ -754,6 +853,14 @@
      }
  
      public void tickNonPassenger(Entity entity) {
++        // Spigot start
++        if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
++            entity.tickCount++;
++            entity.inactiveTick();
++            return;
++        }
++        // Spigot end
 +        entity.tickTimer.startTiming(); // Spigot
          entity.setOldPosAndRot();
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
-@@ -763,6 +862,7 @@
+@@ -763,6 +870,7 @@
          });
          gameprofilerfiller.incrementCounter("tickNonPassenger");
          entity.tick();
@@ -395,7 +403,7 @@
          gameprofilerfiller.pop();
          Iterator iterator = entity.getPassengers().iterator();
  
-@@ -771,6 +871,7 @@
+@@ -771,6 +879,7 @@
  
              this.tickPassenger(entity, entity1);
          }
@@ -403,7 +411,7 @@
  
      }
  
-@@ -786,6 +887,7 @@
+@@ -786,6 +895,7 @@
                  });
                  gameprofilerfiller.incrementCounter("tickPassenger");
                  passenger.rideTick();
@@ -411,7 +419,7 @@
                  gameprofilerfiller.pop();
                  Iterator iterator = passenger.getPassengers().iterator();
  
-@@ -810,6 +912,7 @@
+@@ -810,6 +920,7 @@
          ServerChunkCache chunkproviderserver = this.getChunkSource();
  
          if (!savingDisabled) {
@@ -419,7 +427,7 @@
              if (progressListener != null) {
                  progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
              }
-@@ -827,11 +930,19 @@
+@@ -827,11 +938,19 @@
              }
  
          }
@@ -440,7 +448,7 @@
          }
  
          DimensionDataStorage worldpersistentdata = this.getChunkSource().getDataStorage();
-@@ -903,18 +1014,40 @@
+@@ -903,18 +1022,40 @@
  
      @Override
      public boolean addFreshEntity(Entity entity) {
@@ -484,7 +492,7 @@
          }
  
      }
-@@ -939,24 +1072,38 @@
+@@ -939,24 +1080,38 @@
          this.entityManager.addNewEntity(player);
      }
  
@@ -527,7 +535,7 @@
              return true;
          }
      }
-@@ -967,13 +1114,35 @@
+@@ -967,13 +1122,35 @@
      }
  
      public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) {
@@ -564,7 +572,7 @@
          while (iterator.hasNext()) {
              ServerPlayer entityplayer = (ServerPlayer) iterator.next();
  
-@@ -982,6 +1151,12 @@
+@@ -982,6 +1159,12 @@
                  double d1 = (double) pos.getY() - entityplayer.getY();
                  double d2 = (double) pos.getZ() - entityplayer.getZ();
  
@@ -577,7 +585,7 @@
                  if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
                      entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress));
                  }
-@@ -1060,7 +1235,18 @@
+@@ -1060,7 +1243,18 @@
              Iterator iterator = this.navigatingMobs.iterator();
  
              while (iterator.hasNext()) {
@@ -597,7 +605,7 @@
                  PathNavigation navigationabstract = entityinsentient.getNavigation();
  
                  if (navigationabstract.shouldRecomputePath(pos)) {
-@@ -1126,9 +1312,15 @@
+@@ -1126,9 +1320,15 @@
  
      @Override
      public void explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions smallParticle, ParticleOptions largeParticle, Holder<SoundEvent> soundEvent) {
@@ -614,7 +622,7 @@
              case NONE:
                  explosion_effect = Explosion.BlockInteraction.KEEP;
                  break;
-@@ -1144,16 +1336,26 @@
+@@ -1144,16 +1344,26 @@
              case TRIGGER:
                  explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK;
                  break;
@@ -644,7 +652,7 @@
          Iterator iterator = this.players.iterator();
  
          while (iterator.hasNext()) {
-@@ -1162,10 +1364,11 @@
+@@ -1162,10 +1372,11 @@
              if (entityplayer.distanceToSqr(vec3d) < 4096.0D) {
                  Optional<Vec3> optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer));
  
@@ -657,7 +665,7 @@
      }
  
      private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayRule) {
-@@ -1226,17 +1429,24 @@
+@@ -1226,17 +1437,24 @@
      }
  
      public <T extends ParticleOptions> int sendParticles(T parameters, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) {
@@ -685,7 +693,7 @@
                  ++j;
              }
          }
-@@ -1292,7 +1502,7 @@
+@@ -1292,7 +1510,7 @@
  
      @Nullable
      public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) {
@@ -694,7 +702,7 @@
              return null;
          } else {
              Optional<HolderSet.Named<Structure>> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag);
-@@ -1334,11 +1544,22 @@
+@@ -1334,11 +1552,22 @@
      @Nullable
      @Override
      public MapItemSavedData getMapData(MapId id) {
@@ -718,7 +726,7 @@
          this.getServer().overworld().getDataStorage().set(id.key(), state);
      }
  
-@@ -1649,6 +1870,11 @@
+@@ -1649,6 +1878,11 @@
      @Override
      public void blockUpdated(BlockPos pos, Block block) {
          if (!this.isDebug()) {
@@ -730,7 +738,7 @@
              this.updateNeighborsAt(pos, block);
          }
  
-@@ -1668,12 +1894,12 @@
+@@ -1668,12 +1902,12 @@
      }
  
      public boolean isFlat() {
@@ -745,7 +753,7 @@
      }
  
      @Nullable
-@@ -1696,7 +1922,7 @@
+@@ -1696,7 +1930,7 @@
      private static <T> String getTypeCount(Iterable<T> items, Function<T, String> classifier) {
          try {
              Object2IntOpenHashMap<String> object2intopenhashmap = new Object2IntOpenHashMap();
@@ -754,7 +762,7 @@
  
              while (iterator.hasNext()) {
                  T t0 = iterator.next();
-@@ -1705,7 +1931,7 @@
+@@ -1705,7 +1939,7 @@
                  object2intopenhashmap.addTo(s, 1);
              }
  
@@ -763,7 +771,7 @@
                  String s1 = (String) entry.getKey();
  
                  return s1 + ":" + entry.getIntValue();
-@@ -1717,6 +1943,7 @@
+@@ -1717,6 +1951,7 @@
  
      @Override
      public LevelEntityGetter<Entity> getEntities() {
@@ -771,7 +779,7 @@
          return this.entityManager.getEntityGetter();
      }
  
-@@ -1836,6 +2063,7 @@
+@@ -1836,6 +2071,7 @@
          }
  
          public void onTrackingStart(Entity entity) {
@@ -779,7 +787,7 @@
              ServerLevel.this.getChunkSource().addEntity(entity);
              if (entity instanceof ServerPlayer entityplayer) {
                  ServerLevel.this.players.add(entityplayer);
-@@ -1864,9 +2092,12 @@
+@@ -1864,9 +2100,12 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
@@ -792,7 +800,7 @@
              ServerLevel.this.getChunkSource().removeEntity(entity);
              if (entity instanceof ServerPlayer entityplayer) {
                  ServerLevel.this.players.remove(entityplayer);
-@@ -1895,6 +2126,14 @@
+@@ -1895,6 +2134,14 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
index 525e6fc399..b04a563fc0 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/AgeableMob.java
 +++ b/net/minecraft/world/entity/AgeableMob.java
-@@ -21,6 +21,7 @@
+@@ -21,12 +21,38 @@
      protected int age;
      protected int forcedAge;
      protected int forcedAgeTimer;
@@ -8,7 +8,38 @@
  
      protected AgeableMob(EntityType<? extends AgeableMob> type, Level world) {
          super(type, world);
-@@ -104,6 +105,7 @@
+     }
+ 
++    // Spigot start
+     @Override
++    public void inactiveTick()
++    {
++        super.inactiveTick();
++        if ( this.level().isClientSide || this.ageLocked )
++        { // CraftBukkit
++            this.refreshDimensions();
++        } else
++        {
++            int i = this.getAge();
++
++            if ( i < 0 )
++            {
++                ++i;
++                this.setAge( i );
++            } else if ( i > 0 )
++            {
++                --i;
++                this.setAge( i );
++            }
++        }
++    }
++    // Spigot end
++
++    @Override
+     public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) {
+         if (entityData == null) {
+             entityData = new AgeableMob.AgeableMobGroupData(true);
+@@ -104,6 +130,7 @@
          super.addAdditionalSaveData(nbt);
          nbt.putInt("Age", this.getAge());
          nbt.putInt("ForcedAge", this.forcedAge);
@@ -16,7 +47,7 @@
      }
  
      @Override
-@@ -111,6 +113,7 @@
+@@ -111,6 +138,7 @@
          super.readAdditionalSaveData(nbt);
          this.setAge(nbt.getInt("Age"));
          this.forcedAge = nbt.getInt("ForcedAge");
@@ -24,7 +55,7 @@
      }
  
      @Override
-@@ -125,7 +128,7 @@
+@@ -125,7 +153,7 @@
      @Override
      public void aiStep() {
          super.aiStep();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
index 3ec6f8cfc8..835489e22c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -22,7 +22,27 @@
      @Nullable
      public UUID ownerUUID;
  
-@@ -200,7 +206,7 @@
+@@ -145,7 +151,19 @@
+         this.duration = duration;
+     }
+ 
++    // Spigot start - copied from below
+     @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++
++        if (this.tickCount >= this.waitTime + this.duration) {
++            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++            return;
++        }
++    }
++    // Spigot end
++
++    @Override
+     public void tick() {
+         super.tick();
+         Level world = this.level();
+@@ -200,7 +218,7 @@
  
      private void serverTick(ServerLevel world) {
          if (this.tickCount >= this.waitTime + this.duration) {
@@ -31,7 +51,7 @@
          } else {
              boolean flag = this.isWaiting();
              boolean flag1 = this.tickCount < this.waitTime;
-@@ -215,7 +221,7 @@
+@@ -215,7 +233,7 @@
                  if (this.radiusPerTick != 0.0F) {
                      f += this.radiusPerTick;
                      if (f < 0.5F) {
@@ -40,7 +60,7 @@
                          return;
                      }
  
-@@ -244,16 +250,17 @@
+@@ -244,16 +262,17 @@
                          }
  
                          list.addAll(this.potionContents.customEffects());
@@ -61,7 +81,7 @@
  
                                      Objects.requireNonNull(entityliving);
                                      if (!stream.noneMatch(entityliving::canBeAffected)) {
-@@ -262,6 +269,19 @@
+@@ -262,6 +281,19 @@
                                          double d2 = d0 * d0 + d1 * d1;
  
                                          if (d2 <= (double) (f * f)) {
@@ -81,7 +101,7 @@
                                              this.victims.put(entityliving, this.tickCount + this.reapplicationDelay);
                                              Iterator iterator2 = list.iterator();
  
-@@ -271,14 +291,14 @@
+@@ -271,14 +303,14 @@
                                                  if (((MobEffect) mobeffect1.getEffect().value()).isInstantenous()) {
                                                      ((MobEffect) mobeffect1.getEffect().value()).applyInstantenousEffect(world, this, this.getOwner(), entityliving, mobeffect1.getAmplifier(), 0.5D);
                                                  } else {
@@ -98,7 +118,7 @@
                                                      return;
                                                  }
  
-@@ -288,7 +308,7 @@
+@@ -288,7 +320,7 @@
                                              if (this.durationOnUse != 0) {
                                                  this.duration += this.durationOnUse;
                                                  if (this.duration <= 0) {
@@ -107,7 +127,7 @@
                                                      return;
                                                  }
                                              }
-@@ -336,14 +356,14 @@
+@@ -336,14 +368,14 @@
          this.waitTime = waitTime;
      }
  
@@ -124,7 +144,7 @@
          if (this.owner != null && !this.owner.isRemoved()) {
              return this.owner;
          } else {
-@@ -353,10 +373,10 @@
+@@ -353,10 +385,10 @@
                  if (world instanceof ServerLevel) {
                      ServerLevel worldserver = (ServerLevel) world;
                      Entity entity = worldserver.getEntity(this.ownerUUID);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 092c71cb8f..61b2c21d59 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -96,7 +96,7 @@
      private static final EntityDataAccessor<Integer> DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
      private EntityInLevelCallback levelCallback;
      private final VecDeltaCodec packetPositionCodec;
-@@ -253,7 +312,32 @@
+@@ -253,6 +312,37 @@
      private final List<Entity.Movement> movementThisTick;
      private final Set<BlockState> blocksInside;
      private final LongSet visitedBlocks;
@@ -116,7 +116,13 @@
 +    // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
 +    public boolean pluginRemoved = false;
 +    public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot
- 
++    // Spigot start
++    public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
++    public final boolean defaultActivationState;
++    public long activatedTick = Integer.MIN_VALUE;
++    public void inactiveTick() { }
++    // Spigot end
++
 +    public float getBukkitYaw() {
 +        return this.yRot;
 +    }
@@ -125,11 +131,24 @@
 +        return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
 +    }
 +    // CraftBukkit end
-+
+ 
      public Entity(EntityType<?> type, Level world) {
          this.id = Entity.ENTITY_COUNTER.incrementAndGet();
-         this.passengers = ImmutableList.of();
-@@ -292,7 +376,7 @@
+@@ -284,6 +374,13 @@
+         this.position = Vec3.ZERO;
+         this.blockPosition = BlockPos.ZERO;
+         this.chunkPosition = ChunkPos.ZERO;
++        // Spigot start
++        if (world != null) {
++            this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig);
++        } else {
++            this.defaultActivationState = false;
++        }
++        // Spigot end
+         SynchedEntityData.Builder datawatcher_a = new SynchedEntityData.Builder(this);
+ 
+         datawatcher_a.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0);
+@@ -292,7 +389,7 @@
          datawatcher_a.define(Entity.DATA_CUSTOM_NAME, Optional.empty());
          datawatcher_a.define(Entity.DATA_SILENT, false);
          datawatcher_a.define(Entity.DATA_NO_GRAVITY, false);
@@ -138,7 +157,7 @@
          datawatcher_a.define(Entity.DATA_TICKS_FROZEN, 0);
          this.defineSynchedData(datawatcher_a);
          this.entityData = datawatcher_a.build();
-@@ -362,20 +446,36 @@
+@@ -362,20 +459,36 @@
      }
  
      public void kill(ServerLevel world) {
@@ -177,7 +196,7 @@
      public boolean equals(Object object) {
          return object instanceof Entity ? ((Entity) object).id == this.id : false;
      }
-@@ -385,22 +485,34 @@
+@@ -385,22 +498,34 @@
      }
  
      public void remove(Entity.RemovalReason reason) {
@@ -217,7 +236,7 @@
          return this.getPose() == pose;
      }
  
-@@ -417,6 +529,33 @@
+@@ -417,6 +542,33 @@
      }
  
      public void setRot(float yaw, float pitch) {
@@ -251,7 +270,7 @@
          this.setYRot(yaw % 360.0F);
          this.setXRot(pitch % 360.0F);
      }
-@@ -462,6 +601,15 @@
+@@ -462,6 +614,15 @@
          this.baseTick();
      }
  
@@ -267,7 +286,7 @@
      public void baseTick() {
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
-@@ -475,7 +623,7 @@
+@@ -475,7 +636,7 @@
              --this.boardingCooldown;
          }
  
@@ -276,7 +295,7 @@
          if (this.canSpawnSprintParticle()) {
              this.spawnSprintParticle();
          }
-@@ -514,6 +662,10 @@
+@@ -514,6 +675,10 @@
          if (this.isInLava()) {
              this.lavaHurt();
              this.fallDistance *= 0.5F;
@@ -287,7 +306,7 @@
          }
  
          this.checkBelowWorld();
-@@ -525,7 +677,7 @@
+@@ -525,7 +690,7 @@
          world = this.level();
          if (world instanceof ServerLevel worldserver) {
              if (this instanceof Leashable) {
@@ -296,7 +315,7 @@
              }
          }
  
-@@ -568,15 +720,32 @@
+@@ -568,15 +733,32 @@
  
      public void lavaHurt() {
          if (!this.fireImmune()) {
@@ -331,7 +350,7 @@
              }
  
          }
-@@ -587,9 +756,25 @@
+@@ -587,9 +769,25 @@
      }
  
      public final void igniteForSeconds(float seconds) {
@@ -358,7 +377,7 @@
      public void igniteForTicks(int ticks) {
          if (this.remainingFireTicks < ticks) {
              this.setRemainingFireTicks(ticks);
-@@ -610,7 +795,7 @@
+@@ -610,7 +808,7 @@
      }
  
      protected void onBelowWorld() {
@@ -367,7 +386,7 @@
      }
  
      public boolean isFree(double offsetX, double offsetY, double offsetZ) {
-@@ -672,6 +857,7 @@
+@@ -672,6 +870,7 @@
      }
  
      public void move(MoverType type, Vec3 movement) {
@@ -375,10 +394,13 @@
          if (this.noPhysics) {
              this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
          } else {
-@@ -750,6 +936,28 @@
-                     }
-                 }
+@@ -747,8 +946,30 @@
  
+                     if (movement.y != vec3d1.y) {
+                         block.updateEntityMovementAfterFallOn(this.level(), this);
++                    }
++                }
++
 +                // CraftBukkit start
 +                if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) {
 +                    Vehicle vehicle = (Vehicle) this.getBukkitEntity();
@@ -392,19 +414,18 @@
 +                        bl = bl.getRelative(BlockFace.SOUTH);
 +                    } else if (movement.z < vec3d1.z) {
 +                        bl = bl.getRelative(BlockFace.NORTH);
-+                    }
+                     }
 +
 +                    if (!bl.getType().isAir()) {
 +                        VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl);
 +                        this.level.getCraftServer().getPluginManager().callEvent(event);
 +                    }
-+                }
+                 }
 +                // CraftBukkit end
-+
+ 
                  if (!this.level().isClientSide() || this.isControlledByLocalInstance()) {
                      Entity.MovementEmission entity_movementemission = this.getMovementEmission();
- 
-@@ -764,6 +972,7 @@
+@@ -764,6 +985,7 @@
                  gameprofilerfiller.pop();
              }
          }
@@ -412,10 +433,11 @@
      }
  
      private void applyMovementEmissionAndPlaySound(Entity.MovementEmission moveEffect, Vec3 movement, BlockPos landingPos, BlockState landingState) {
-@@ -1132,6 +1341,20 @@
+@@ -1131,8 +1353,22 @@
+ 
      protected SoundEvent getSwimHighSpeedSplashSound() {
          return SoundEvents.GENERIC_SPLASH;
-     }
++    }
 +
 +    // CraftBukkit start - Add delegate methods
 +    public SoundEvent getSwimSound0() {
@@ -424,16 +446,17 @@
 +
 +    public SoundEvent getSwimSplashSound0() {
 +        return this.getSwimSplashSound();
-+    }
-+
+     }
+ 
 +    public SoundEvent getSwimHighSpeedSplashSound0() {
 +        return this.getSwimHighSpeedSplashSound();
 +    }
 +    // CraftBukkit end
- 
++
      public void recordMovementThroughBlocks(Vec3 oldPos, Vec3 newPos) {
          this.movementThisTick.add(new Entity.Movement(oldPos, newPos));
-@@ -1609,6 +1832,7 @@
+     }
+@@ -1609,6 +1845,7 @@
          this.yo = y;
          this.zo = d4;
          this.setPos(d3, y, d4);
@@ -441,20 +464,21 @@
      }
  
      public void moveTo(Vec3 pos) {
-@@ -1861,6 +2085,12 @@
-         return false;
-     }
+@@ -1859,7 +2096,13 @@
  
+     public boolean isPushable() {
+         return false;
++    }
++
 +    // CraftBukkit start - collidable API
 +    public boolean canCollideWithBukkit(Entity entity) {
 +        return this.isPushable();
-+    }
+     }
 +    // CraftBukkit end
-+
+ 
      public void awardKillScore(Entity entityKilled, DamageSource damageSource) {
          if (entityKilled instanceof ServerPlayer) {
-             CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource);
-@@ -1889,16 +2119,22 @@
+@@ -1889,16 +2132,22 @@
      }
  
      public boolean saveAsPassenger(CompoundTag nbt) {
@@ -480,7 +504,7 @@
                  return true;
              }
          }
-@@ -1909,54 +2145,97 @@
+@@ -1909,54 +2158,97 @@
      }
  
      public CompoundTag saveWithoutId(CompoundTag nbt) {
@@ -598,7 +622,7 @@
              }
  
              ListTag nbttaglist;
-@@ -1972,10 +2251,10 @@
+@@ -1972,10 +2264,10 @@
                      nbttaglist.add(StringTag.valueOf(s));
                  }
  
@@ -611,7 +635,7 @@
              if (this.isVehicle()) {
                  nbttaglist = new ListTag();
                  iterator = this.getPassengers().iterator();
-@@ -1984,17 +2263,22 @@
+@@ -1984,17 +2276,22 @@
                      Entity entity = (Entity) iterator.next();
                      CompoundTag nbttagcompound1 = new CompoundTag();
  
@@ -637,7 +661,7 @@
          } catch (Throwable throwable) {
              CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
              CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being saved");
-@@ -2080,6 +2364,45 @@
+@@ -2080,6 +2377,45 @@
              } else {
                  throw new IllegalStateException("Entity has invalid position");
              }
@@ -683,7 +707,7 @@
          } catch (Throwable throwable) {
              CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
              CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded");
-@@ -2101,6 +2424,12 @@
+@@ -2101,6 +2437,12 @@
          return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null;
      }
  
@@ -696,7 +720,7 @@
      protected abstract void readAdditionalSaveData(CompoundTag nbt);
  
      protected abstract void addAdditionalSaveData(CompoundTag nbt);
-@@ -2153,9 +2482,22 @@
+@@ -2153,9 +2495,22 @@
          if (stack.isEmpty()) {
              return null;
          } else {
@@ -719,7 +743,7 @@
              world.addFreshEntity(entityitem);
              return entityitem;
          }
-@@ -2184,6 +2526,12 @@
+@@ -2184,6 +2539,12 @@
          if (this.isAlive() && this instanceof Leashable leashable) {
              if (leashable.getLeashHolder() == player) {
                  if (!this.level().isClientSide()) {
@@ -732,7 +756,7 @@
                      if (player.hasInfiniteMaterials()) {
                          leashable.removeLeash();
                      } else {
-@@ -2200,6 +2548,13 @@
+@@ -2200,6 +2561,13 @@
  
              if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
                  if (!this.level().isClientSide()) {
@@ -746,7 +770,7 @@
                      leashable.setLeashedTo(player, true);
                  }
  
-@@ -2265,7 +2620,7 @@
+@@ -2265,7 +2633,7 @@
      }
  
      public boolean showVehicleHealth() {
@@ -755,7 +779,7 @@
      }
  
      public boolean startRiding(Entity entity, boolean force) {
-@@ -2273,7 +2628,7 @@
+@@ -2273,7 +2641,7 @@
              return false;
          } else if (!entity.couldAcceptPassenger()) {
              return false;
@@ -764,7 +788,7 @@
              return false;
          } else {
              for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) {
-@@ -2285,11 +2640,32 @@
+@@ -2285,11 +2653,32 @@
              if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) {
                  return false;
              } else {
@@ -798,7 +822,7 @@
                  this.vehicle = entity;
                  this.vehicle.addPassenger(this);
                  entity.getIndirectPassengersStream().filter((entity2) -> {
-@@ -2318,7 +2694,7 @@
+@@ -2318,7 +2707,7 @@
              Entity entity = this.vehicle;
  
              this.vehicle = null;
@@ -807,7 +831,7 @@
          }
  
      }
-@@ -2349,21 +2725,50 @@
+@@ -2349,21 +2738,50 @@
          }
      }
  
@@ -864,7 +888,7 @@
      }
  
      protected boolean canAddPassenger(Entity passenger) {
-@@ -2464,7 +2869,7 @@
+@@ -2464,7 +2882,7 @@
                      if (teleporttransition != null) {
                          ServerLevel worldserver1 = teleporttransition.newLevel();
  
@@ -873,7 +897,7 @@
                              this.teleport(teleporttransition);
                          }
                      }
-@@ -2547,7 +2952,7 @@
+@@ -2547,7 +2965,7 @@
      }
  
      public boolean isCrouching() {
@@ -882,7 +906,7 @@
      }
  
      public boolean isSprinting() {
-@@ -2563,7 +2968,7 @@
+@@ -2563,7 +2981,7 @@
      }
  
      public boolean isVisuallySwimming() {
@@ -891,7 +915,7 @@
      }
  
      public boolean isVisuallyCrawling() {
-@@ -2571,6 +2976,13 @@
+@@ -2571,6 +2989,13 @@
      }
  
      public void setSwimming(boolean swimming) {
@@ -905,7 +929,7 @@
          this.setSharedFlag(4, swimming);
      }
  
-@@ -2624,8 +3036,12 @@
+@@ -2624,8 +3049,12 @@
          return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false;
      }
  
@@ -919,7 +943,7 @@
      }
  
      public boolean getSharedFlag(int index) {
-@@ -2644,7 +3060,7 @@
+@@ -2644,7 +3073,7 @@
      }
  
      public int getMaxAirSupply() {
@@ -928,7 +952,7 @@
      }
  
      public int getAirSupply() {
-@@ -2652,7 +3068,18 @@
+@@ -2652,7 +3081,18 @@
      }
  
      public void setAirSupply(int air) {
@@ -948,7 +972,7 @@
      }
  
      public int getTicksFrozen() {
-@@ -2679,11 +3106,40 @@
+@@ -2679,11 +3119,40 @@
  
      public void thunderHit(ServerLevel world, LightningBolt lightning) {
          this.setRemainingFireTicks(this.remainingFireTicks + 1);
@@ -991,7 +1015,7 @@
      }
  
      public void onAboveBubbleCol(boolean drag) {
-@@ -2713,7 +3169,7 @@
+@@ -2713,7 +3182,7 @@
          this.resetFallDistance();
      }
  
@@ -1000,7 +1024,7 @@
          return true;
      }
  
-@@ -2852,6 +3308,18 @@
+@@ -2852,6 +3321,18 @@
  
          if (world instanceof ServerLevel worldserver) {
              if (!this.isRemoved()) {
@@ -1019,7 +1043,7 @@
                  ServerLevel worldserver1 = teleportTarget.newLevel();
                  boolean flag = worldserver1.dimension() != worldserver.dimension();
  
-@@ -2920,8 +3388,12 @@
+@@ -2920,8 +3401,12 @@
          } else {
              entity.restoreFrom(this);
              this.removeAfterChangingDimensions();
@@ -1033,7 +1057,7 @@
              Iterator iterator1 = list1.iterator();
  
              while (iterator1.hasNext()) {
-@@ -2947,7 +3419,7 @@
+@@ -2947,7 +3432,7 @@
      }
  
      private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTarget) {
@@ -1042,7 +1066,7 @@
          Iterator iterator = this.getIndirectPassengers().iterator();
  
          while (iterator.hasNext()) {
-@@ -2995,8 +3467,9 @@
+@@ -2995,8 +3480,9 @@
      }
  
      protected void removeAfterChangingDimensions() {
@@ -1053,12 +1077,10 @@
              leashable.removeLeash();
          }
  
-@@ -3004,7 +3477,21 @@
- 
-     public Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) {
+@@ -3006,6 +3492,20 @@
          return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose()));
-+    }
-+
+     }
+ 
 +    // CraftBukkit start
 +    public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
 +        org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
@@ -1070,12 +1092,13 @@
 +            return null;
 +        }
 +        return new CraftPortalEvent(event);
-     }
++    }
 +    // CraftBukkit end
- 
++
      public boolean canUsePortal(boolean allowVehicles) {
          return (allowVehicles || !this.isPassenger()) && this.isAlive();
-@@ -3134,10 +3621,16 @@
+     }
+@@ -3134,9 +3634,15 @@
          return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE);
      }
  
@@ -1086,16 +1109,15 @@
 +    public final boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) {
 +        return this.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN);
 +    }
- 
++
 +    public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<Relative> set, float f, float f1, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
 +        float f2 = Mth.clamp(f1, -90.0F, 90.0F);
 +        Entity entity = this.teleport(new TeleportTransition(worldserver, new Vec3(d0, d1, d2), Vec3.ZERO, f, f2, set, TeleportTransition.DO_NOTHING, cause));
 +        // CraftBukkit end
-+
+ 
          return entity != null;
      }
- 
-@@ -3187,7 +3680,7 @@
+@@ -3187,7 +3693,7 @@
      /** @deprecated */
      @Deprecated
      protected void fixupDimensions() {
@@ -1104,7 +1126,7 @@
          EntityDimensions entitysize = this.getDimensions(entitypose);
  
          this.dimensions = entitysize;
-@@ -3196,7 +3689,7 @@
+@@ -3196,7 +3702,7 @@
  
      public void refreshDimensions() {
          EntityDimensions entitysize = this.dimensions;
@@ -1113,7 +1135,7 @@
          EntityDimensions entitysize1 = this.getDimensions(entitypose);
  
          this.dimensions = entitysize1;
-@@ -3258,10 +3751,29 @@
+@@ -3258,10 +3764,29 @@
      }
  
      public final void setBoundingBox(AABB boundingBox) {
@@ -1145,7 +1167,7 @@
          return this.getDimensions(pose).eyeHeight();
      }
  
-@@ -3335,7 +3847,7 @@
+@@ -3335,7 +3860,7 @@
      }
  
      @Nullable
@@ -1154,7 +1176,7 @@
          return null;
      }
  
-@@ -3435,7 +3947,7 @@
+@@ -3435,7 +3960,7 @@
      }
  
      public boolean isControlledByLocalInstance() {
@@ -1163,7 +1185,7 @@
  
          if (entityliving instanceof Player entityhuman) {
              return entityhuman.isLocalPlayer();
-@@ -3445,7 +3957,7 @@
+@@ -3445,7 +3970,7 @@
      }
  
      public boolean isControlledByClient() {
@@ -1172,7 +1194,7 @@
  
          return entityliving != null && entityliving.isControlledByClient();
      }
-@@ -3463,7 +3975,7 @@
+@@ -3463,7 +3988,7 @@
          return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3);
      }
  
@@ -1181,7 +1203,7 @@
          return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ());
      }
  
-@@ -3489,8 +4001,37 @@
+@@ -3489,8 +4014,37 @@
          return 1;
      }
  
@@ -1220,19 +1242,20 @@
      }
  
      public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) {
-@@ -3551,6 +4092,11 @@
+@@ -3550,7 +4104,12 @@
+ 
                                      vec3d = vec3d.add(vec3d1);
                                      ++k1;
-                                 }
++                                }
 +                                // CraftBukkit start - store last lava contact location
 +                                if (tag == FluidTags.LAVA) {
 +                                    this.lastLavaContact = blockposition_mutableblockposition.immutable();
-+                                }
+                                 }
 +                                // CraftBukkit end
                              }
                          }
                      }
-@@ -3613,7 +4159,7 @@
+@@ -3613,7 +4172,7 @@
          return new ClientboundAddEntityPacket(this, entityTrackerEntry);
      }
  
@@ -1241,7 +1264,7 @@
          return this.type.getDimensions();
      }
  
-@@ -3818,8 +4364,16 @@
+@@ -3818,8 +4377,16 @@
  
      @Override
      public final void setRemoved(Entity.RemovalReason reason) {
@@ -1259,7 +1282,7 @@
          }
  
          if (this.removalReason.shouldDestroy()) {
-@@ -3827,8 +4381,8 @@
+@@ -3827,8 +4394,8 @@
          }
  
          this.getPassengers().forEach(Entity::stopRiding);
@@ -1270,7 +1293,7 @@
      }
  
      public void unsetRemoved() {
-@@ -3887,7 +4441,7 @@
+@@ -3887,7 +4454,7 @@
      }
  
      public Vec3 getKnownMovement() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index c8dd6f4966..251c921b81 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -17,10 +17,11 @@
  import net.minecraft.world.entity.projectile.AbstractArrow;
  import net.minecraft.world.entity.projectile.Projectile;
  import net.minecraft.world.item.AxeItem;
-@@ -136,6 +137,32 @@
+@@ -135,6 +136,32 @@
+ import net.minecraft.world.scores.PlayerTeam;
  import net.minecraft.world.scores.Scoreboard;
  import org.slf4j.Logger;
- 
++
 +// CraftBukkit start
 +import java.util.ArrayList;
 +import java.util.HashSet;
@@ -46,10 +47,9 @@
 +// CraftBukkit end
 +
 +import org.bukkit.craftbukkit.SpigotTimings; // Spigot
-+
+ 
  public abstract class LivingEntity extends Entity implements Attackable {
  
-     private static final Logger LOGGER = LogUtils.getLogger();
 @@ -174,7 +201,7 @@
      public static final float DEFAULT_BABY_SCALE = 0.5F;
      public static final String ATTRIBUTES_FIELD = "attributes";
@@ -68,7 +68,7 @@
      public int lastHurtByPlayerTime;
      protected boolean dead;
      protected int noActionTime;
-@@ -260,7 +287,20 @@
+@@ -260,7 +287,27 @@
      protected boolean skipDropExperience;
      private final EnumMap<EquipmentSlot, Reference2ObjectMap<Enchantment, Set<EnchantmentLocationBasedEffect>>> activeLocationDependentEnchantments;
      protected float appliedScale;
@@ -85,11 +85,18 @@
 +        return this.getYHeadRot();
 +    }
 +    // CraftBukkit end
++    // Spigot start
++    public void inactiveTick()
++    {
++        super.inactiveTick();
++        ++this.noActionTime; // Above all the floats
++    }
++    // Spigot end
 +
      protected LivingEntity(EntityType<? extends LivingEntity> type, Level world) {
          super(type, world);
          this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY);
-@@ -276,7 +316,9 @@
+@@ -276,7 +323,9 @@
          this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class);
          this.appliedScale = 1.0F;
          this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
@@ -100,7 +107,7 @@
          this.blocksBuilding = true;
          this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
          this.reapplyPosition();
-@@ -356,7 +398,13 @@
+@@ -356,7 +405,13 @@
                      double d8 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
                      int i = (int) (150.0D * d8);
  
@@ -115,7 +122,7 @@
                  }
              }
          }
-@@ -402,7 +450,7 @@
+@@ -402,7 +457,7 @@
          }
  
          if (this.isAlive()) {
@@ -124,7 +131,7 @@
              Level world1 = this.level();
              ServerLevel worldserver1;
              double d0;
-@@ -424,7 +472,7 @@
+@@ -424,7 +479,7 @@
              }
  
              if (this.isEyeInFluid(FluidTags.WATER) && !this.level().getBlockState(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) {
@@ -133,7 +140,7 @@
  
                  if (flag1) {
                      this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
-@@ -573,7 +621,7 @@
+@@ -573,7 +628,7 @@
          ++this.deathTime;
          if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) {
              this.level().broadcastEntityEvent(this, (byte) 60);
@@ -142,7 +149,7 @@
          }
  
      }
-@@ -629,7 +677,7 @@
+@@ -629,7 +684,7 @@
          return this.lastHurtByMobTimestamp;
      }
  
@@ -151,27 +158,28 @@
          this.lastHurtByPlayer = attacking;
          this.lastHurtByPlayerTime = this.tickCount;
      }
-@@ -679,17 +727,23 @@
+@@ -679,17 +734,23 @@
      }
  
      public void onEquipItem(EquipmentSlot slot, ItemStack oldStack, ItemStack newStack) {
+-        if (!this.level().isClientSide() && !this.isSpectator()) {
+-            boolean flag = newStack.isEmpty() && oldStack.isEmpty();
 +        // CraftBukkit start
 +        this.onEquipItem(slot, oldStack, newStack, false);
 +    }
-+
-+    public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) {
-+        // CraftBukkit end
-         if (!this.level().isClientSide() && !this.isSpectator()) {
--            boolean flag = newStack.isEmpty() && oldStack.isEmpty();
-+            boolean flag = itemstack1.isEmpty() && itemstack.isEmpty();
  
 -            if (!flag && !ItemStack.isSameItemSameComponents(oldStack, newStack) && !this.firstTick) {
 -                Equippable equippable = (Equippable) newStack.get(DataComponents.EQUIPPABLE);
-+            if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) {
-+                Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE);
++    public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) {
++        // CraftBukkit end
++        if (!this.level().isClientSide() && !this.isSpectator()) {
++            boolean flag = itemstack1.isEmpty() && itemstack.isEmpty();
  
 -                if (!this.isSilent() && equippable != null && slot == equippable.slot()) {
 -                    this.level().playSeededSound((Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong());
++            if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) {
++                Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE);
++
 +                if (!this.isSilent() && equippable != null && enumitemslot == equippable.slot() && !silent) { // CraftBukkit
 +                    this.level().playSeededSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong());
                  }
@@ -181,7 +189,7 @@
                      this.gameEvent(equippable != null ? GameEvent.EQUIP : GameEvent.UNEQUIP);
                  }
  
-@@ -699,17 +753,24 @@
+@@ -699,17 +760,24 @@
  
      @Override
      public void remove(Entity.RemovalReason reason) {
@@ -209,7 +217,7 @@
          this.brain.clearMemories();
      }
  
-@@ -722,6 +783,7 @@
+@@ -722,6 +790,7 @@
              mobeffect.onMobRemoved(world, this, reason);
          }
  
@@ -217,7 +225,7 @@
          this.activeEffects.clear();
      }
  
-@@ -781,6 +843,17 @@
+@@ -781,6 +850,17 @@
              }
          }
  
@@ -235,7 +243,7 @@
          if (nbt.contains("Health", 99)) {
              this.setHealth(nbt.getFloat("Health"));
          }
-@@ -819,9 +892,32 @@
+@@ -819,9 +899,32 @@
  
      }
  
@@ -268,7 +276,7 @@
          try {
              while (iterator.hasNext()) {
                  Holder<MobEffect> holder = (Holder) iterator.next();
-@@ -831,6 +927,12 @@
+@@ -831,6 +934,12 @@
                      this.onEffectUpdated(mobeffect, true, (Entity) null);
                  })) {
                      if (!this.level().isClientSide) {
@@ -281,7 +289,7 @@
                          iterator.remove();
                          this.onEffectsRemoved(List.of(mobeffect));
                      }
-@@ -841,6 +943,17 @@
+@@ -841,6 +950,17 @@
          } catch (ConcurrentModificationException concurrentmodificationexception) {
              ;
          }
@@ -299,7 +307,7 @@
  
          if (this.effectsDirty) {
              if (!this.level().isClientSide) {
-@@ -921,7 +1034,7 @@
+@@ -921,7 +1041,7 @@
      }
  
      public boolean canAttack(LivingEntity target) {
@@ -308,7 +316,7 @@
      }
  
      public boolean canBeSeenAsEnemy() {
-@@ -952,17 +1065,36 @@
+@@ -952,17 +1072,36 @@
          this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, List.of());
      }
  
@@ -349,7 +357,7 @@
          }
      }
  
-@@ -987,24 +1119,55 @@
+@@ -987,24 +1126,55 @@
          return this.addEffect(effect, (Entity) null);
      }
  
@@ -378,9 +386,6 @@
 +            MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(mobeffect.getEffect());
              boolean flag = false;
  
--            if (mobeffect1 == null) {
--                this.activeEffects.put(effect.getEffect(), effect);
--                this.onEffectAdded(effect, source);
 +            // CraftBukkit start
 +            boolean override = false;
 +            if (mobeffect1 != null) {
@@ -393,7 +398,9 @@
 +            }
 +            // CraftBukkit end
 +
-+            if (mobeffect1 == null) {
+             if (mobeffect1 == null) {
+-                this.activeEffects.put(effect.getEffect(), effect);
+-                this.onEffectAdded(effect, source);
 +                this.activeEffects.put(mobeffect.getEffect(), mobeffect);
 +                this.onEffectAdded(mobeffect, entity);
                  flag = true;
@@ -414,7 +421,7 @@
              return flag;
          }
      }
-@@ -1031,14 +1194,40 @@
+@@ -1031,14 +1201,40 @@
          return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM);
      }
  
@@ -457,7 +464,7 @@
          if (mobeffect != null) {
              this.onEffectsRemoved(List.of(mobeffect));
              return true;
-@@ -1142,20 +1331,55 @@
+@@ -1142,20 +1338,55 @@
  
      }
  
@@ -514,7 +521,7 @@
          this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
      }
  
-@@ -1167,7 +1391,7 @@
+@@ -1167,7 +1398,7 @@
      public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
          if (this.isInvulnerableTo(world, source)) {
              return false;
@@ -523,7 +530,7 @@
              return false;
          } else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
              return false;
-@@ -1182,10 +1406,11 @@
+@@ -1182,10 +1413,11 @@
              }
  
              float f1 = amount;
@@ -537,7 +544,7 @@
                  this.hurtCurrentlyUsedShield(amount);
                  f2 = amount;
                  amount = 0.0F;
-@@ -1202,15 +1427,26 @@
+@@ -1202,15 +1434,26 @@
                  flag = true;
              }
  
@@ -566,7 +573,7 @@
              this.walkAnimation.setSpeed(1.5F);
              if (Float.isNaN(amount) || Float.isInfinite(amount)) {
                  amount = Float.MAX_VALUE;
-@@ -1218,18 +1454,27 @@
+@@ -1218,18 +1461,27 @@
  
              boolean flag1 = true;
  
@@ -598,7 +605,7 @@
                  this.hurtDuration = 10;
                  this.hurtTime = this.hurtDuration;
              }
-@@ -1243,7 +1488,7 @@
+@@ -1243,7 +1495,7 @@
                      world.broadcastDamageEvent(this, source);
                  }
  
@@ -607,7 +614,7 @@
                      this.markHurt();
                  }
  
-@@ -1263,7 +1508,7 @@
+@@ -1263,7 +1515,7 @@
                          d1 = source.getSourcePosition().z() - this.getZ();
                      }
  
@@ -616,7 +623,7 @@
                      if (!flag) {
                          this.indicateDamage(d0, d1);
                      }
-@@ -1282,7 +1527,7 @@
+@@ -1282,7 +1534,7 @@
                  this.playHurtSound(source);
              }
  
@@ -625,7 +632,7 @@
  
              if (flag2) {
                  this.lastDamageSource = source;
-@@ -1329,10 +1574,10 @@
+@@ -1329,10 +1581,10 @@
      }
  
      @Nullable
@@ -638,7 +645,7 @@
              this.lastHurtByPlayerTime = 100;
              this.lastHurtByPlayer = entityhuman;
              return entityhuman;
-@@ -1342,8 +1587,8 @@
+@@ -1342,8 +1594,8 @@
                      this.lastHurtByPlayerTime = 100;
                      LivingEntity entityliving = entitywolf.getOwner();
  
@@ -649,7 +656,7 @@
  
                          this.lastHurtByPlayer = entityhuman1;
                      } else {
-@@ -1363,7 +1608,7 @@
+@@ -1363,7 +1615,7 @@
      }
  
      protected void blockedByShield(LivingEntity target) {
@@ -658,7 +665,7 @@
      }
  
      private boolean checkTotemDeathProtection(DamageSource source) {
-@@ -1375,20 +1620,33 @@
+@@ -1375,20 +1627,33 @@
              InteractionHand[] aenumhand = InteractionHand.values();
              int i = aenumhand.length;
  
@@ -696,7 +703,7 @@
                      ServerPlayer entityplayer = (ServerPlayer) this;
  
                      entityplayer.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
-@@ -1512,14 +1770,22 @@
+@@ -1512,14 +1777,22 @@
                      BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
  
                      if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
@@ -721,7 +728,7 @@
                      this.level().addFreshEntity(entityitem);
                  }
              }
-@@ -1530,22 +1796,37 @@
+@@ -1530,24 +1803,39 @@
      protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
          boolean flag = this.lastHurtByPlayerTime > 0;
  
@@ -751,8 +758,8 @@
          }
  
 +        return 0; // CraftBukkit
-+    }
-+
+     }
+ 
 +    protected void dropExperience(ServerLevel world, @Nullable Entity attacker) {
 +        // CraftBukkit start - Update getExpReward() above if the removed if() changes!
 +        if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
@@ -760,10 +767,12 @@
 +            this.expToDrop = 0;
 +        }
 +        // CraftBukkit end
-     }
- 
++    }
++
      protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {}
-@@ -1612,19 +1893,31 @@
+ 
+     public long getLootTableSeed() {
+@@ -1612,19 +1900,31 @@
      }
  
      public void knockback(double strength, double x, double z) {
@@ -802,7 +811,7 @@
          }
      }
  
-@@ -1683,6 +1976,20 @@
+@@ -1683,6 +1983,20 @@
          return new LivingEntity.Fallsounds(SoundEvents.GENERIC_SMALL_FALL, SoundEvents.GENERIC_BIG_FALL);
      }
  
@@ -823,7 +832,7 @@
      public Optional<BlockPos> getLastClimbablePos() {
          return this.lastClimbablePos;
      }
-@@ -1757,9 +2064,14 @@
+@@ -1757,9 +2071,14 @@
          int i = this.calculateFallDamage(fallDistance, damageMultiplier);
  
          if (i > 0) {
@@ -839,7 +848,7 @@
              return true;
          } else {
              return flag;
-@@ -1830,7 +2142,7 @@
+@@ -1830,7 +2149,7 @@
  
      protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) {
          if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) {
@@ -848,7 +857,7 @@
              amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
          }
  
-@@ -1841,7 +2153,8 @@
+@@ -1841,7 +2160,8 @@
          if (source.is(DamageTypeTags.BYPASSES_EFFECTS)) {
              return amount;
          } else {
@@ -858,7 +867,7 @@
                  int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
                  int j = 25 - i;
                  float f1 = amount * (float) j;
-@@ -1884,18 +2197,144 @@
+@@ -1884,18 +2204,144 @@
          }
      }
  
@@ -1012,7 +1021,7 @@
  
                  if (entity instanceof ServerPlayer) {
                      ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -1904,13 +2343,48 @@
+@@ -1904,13 +2350,48 @@
                  }
              }
  
@@ -1065,7 +1074,7 @@
      }
  
      public CombatTracker getCombatTracker() {
-@@ -1935,8 +2409,18 @@
+@@ -1935,8 +2416,18 @@
      }
  
      public final void setArrowCount(int stuckArrowCount) {
@@ -1085,7 +1094,7 @@
  
      public final int getStingerCount() {
          return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID);
-@@ -1999,7 +2483,7 @@
+@@ -1999,7 +2490,7 @@
                      this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
                  }
  
@@ -1094,20 +1103,20 @@
                      this.setHealth(0.0F);
                      this.die(this.damageSources().generic());
                  }
-@@ -2182,6 +2666,12 @@
+@@ -2181,6 +2672,12 @@
+     public abstract Iterable<ItemStack> getArmorSlots();
  
      public abstract ItemStack getItemBySlot(EquipmentSlot slot);
- 
++
 +    // CraftBukkit start
 +    public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
 +        this.setItemSlot(enumitemslot, itemstack);
 +    }
 +    // CraftBukkit end
-+
+ 
      public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
  
-     public Iterable<ItemStack> getHandSlots() {
-@@ -2494,7 +2984,7 @@
+@@ -2494,7 +2991,7 @@
  
      }
  
@@ -1116,7 +1125,7 @@
          Vec3 vec3d1 = this.getRiddenInput(controllingPlayer, movementInput);
  
          this.tickRidden(controllingPlayer, vec3d1);
-@@ -2507,13 +2997,13 @@
+@@ -2507,13 +3004,13 @@
  
      }
  
@@ -1133,7 +1142,7 @@
          return this.getSpeed();
      }
  
-@@ -2571,7 +3061,7 @@
+@@ -2571,7 +3068,7 @@
              double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D);
              double d2 = Math.max(motion.y, -0.15000000596046448D);
  
@@ -1142,7 +1151,7 @@
                  d2 = 0.0D;
              }
  
-@@ -2586,7 +3076,7 @@
+@@ -2586,7 +3083,7 @@
      }
  
      protected float getFlyingSpeed() {
@@ -1151,7 +1160,7 @@
      }
  
      public float getSpeed() {
-@@ -2604,6 +3094,7 @@
+@@ -2604,6 +3101,7 @@
  
      @Override
      public void tick() {
@@ -1159,7 +1168,7 @@
          super.tick();
          this.updatingUsingItem();
          this.updateSwimAmount();
-@@ -2634,7 +3125,7 @@
+@@ -2634,7 +3132,7 @@
                  }
              }
  
@@ -1168,7 +1177,7 @@
              if (this.tickCount % 20 == 0) {
                  this.getCombatTracker().recheckStatus();
              }
-@@ -2645,7 +3136,9 @@
+@@ -2645,7 +3143,9 @@
          }
  
          if (!this.isRemoved()) {
@@ -1178,7 +1187,7 @@
          }
  
          double d0 = this.getX() - this.xo;
-@@ -2739,9 +3232,10 @@
+@@ -2739,9 +3239,10 @@
          }
  
          this.elytraAnimationState.tick();
@@ -1190,7 +1199,7 @@
          Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
  
          if (map != null) {
-@@ -2945,6 +3439,7 @@
+@@ -2945,6 +3446,7 @@
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
          gameprofilerfiller.push("ai");
@@ -1198,7 +1207,7 @@
          if (this.isImmobile()) {
              this.jumping = false;
              this.xxa = 0.0F;
-@@ -2954,6 +3449,7 @@
+@@ -2954,6 +3456,7 @@
              this.serverAiStep();
              gameprofilerfiller.pop();
          }
@@ -1206,7 +1215,7 @@
  
          gameprofilerfiller.pop();
          gameprofilerfiller.push("jump");
-@@ -2996,11 +3492,12 @@
+@@ -2996,11 +3499,12 @@
              this.resetFallDistance();
          }
  
@@ -1220,7 +1229,7 @@
                  if (this.isAlive()) {
                      this.travelRidden(entityhuman, vec3d1);
                      break label112;
-@@ -3009,6 +3506,7 @@
+@@ -3009,6 +3513,7 @@
  
              this.travel(vec3d1);
          }
@@ -1228,7 +1237,7 @@
  
          if (!this.level().isClientSide() || this.isControlledByLocalInstance()) {
              this.applyEffectsFromBlocks();
-@@ -3044,7 +3542,9 @@
+@@ -3044,7 +3549,9 @@
              this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox());
          }
  
@@ -1238,7 +1247,7 @@
          gameprofilerfiller.pop();
          world = this.level();
          if (world instanceof ServerLevel worldserver) {
-@@ -3063,6 +3563,7 @@
+@@ -3063,6 +3570,7 @@
          this.checkSlowFallDistance();
          if (!this.level().isClientSide) {
              if (!this.canGlide()) {
@@ -1246,7 +1255,7 @@
                  this.setSharedFlag(7, false);
                  return;
              }
-@@ -3113,7 +3614,7 @@
+@@ -3113,7 +3621,7 @@
          Level world = this.level();
  
          if (!(world instanceof ServerLevel worldserver)) {
@@ -1255,7 +1264,7 @@
          } else {
              List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this));
  
-@@ -3305,15 +3806,22 @@
+@@ -3305,15 +3813,22 @@
  
      @Override
      public boolean isPickable() {
@@ -1280,7 +1289,7 @@
      public float getYHeadRot() {
          return this.yHeadRot;
      }
-@@ -3483,8 +3991,31 @@
+@@ -3483,8 +3998,31 @@
                  this.releaseUsingItem();
              } else {
                  if (!this.useItem.isEmpty() && this.isUsingItem()) {
@@ -1313,7 +1322,7 @@
                      if (itemstack != this.useItem) {
                          this.setItemInHand(enumhand, itemstack);
                      }
-@@ -3568,12 +4099,18 @@
+@@ -3568,12 +4106,18 @@
      }
  
      public boolean randomTeleport(double x, double y, double z, boolean particleEffects) {
@@ -1334,7 +1343,7 @@
          Level world = this.level();
  
          if (world.hasChunkAt(blockposition)) {
-@@ -3592,18 +4129,43 @@
+@@ -3592,18 +4136,43 @@
              }
  
              if (flag2) {
@@ -1382,7 +1391,7 @@
                  world.broadcastEntityEvent(this, (byte) 46);
              }
  
-@@ -3613,7 +4175,7 @@
+@@ -3613,7 +4182,7 @@
                  entitycreature.getNavigation().stop();
              }
  
@@ -1391,7 +1400,7 @@
          }
      }
  
-@@ -3706,7 +4268,7 @@
+@@ -3706,7 +4275,7 @@
      }
  
      public void stopSleeping() {
@@ -1400,7 +1409,7 @@
          Level world = this.level();
  
          java.util.Objects.requireNonNull(world);
-@@ -3718,9 +4280,9 @@
+@@ -3718,9 +4287,9 @@
  
                  this.level().setBlock(blockposition, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, false), 3);
                  Vec3 vec3d = (Vec3) BedBlock.findStandUpPosition(this.getType(), this.level(), blockposition, enumdirection, this.getYRot()).orElseGet(() -> {
@@ -1412,7 +1421,7 @@
                  });
                  Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize();
                  float f = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D);
-@@ -3740,7 +4302,7 @@
+@@ -3740,7 +4309,7 @@
  
      @Nullable
      public Direction getBedOrientation() {
@@ -1421,7 +1430,7 @@
  
          return blockposition != null ? BedBlock.getBedOrientation(this.level(), blockposition) : null;
      }
-@@ -3905,7 +4467,7 @@
+@@ -3905,7 +4474,7 @@
      public float maxUpStep() {
          float f = (float) this.getAttributeValue(Attributes.STEP_HEIGHT);
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 54a286345b..b9b409b53b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -94,7 +94,7 @@
  
              this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing();
              if (!this.level().isClientSide) {
-@@ -201,8 +214,14 @@
+@@ -201,12 +214,40 @@
                  }
              }
  
@@ -110,8 +110,34 @@
 +                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
              }
  
++        }
++    }
++
++    // Spigot start - copied from above
++    @Override
++    public void inactiveTick() {
++        // CraftBukkit start - Use wall time for pickup and despawn timers
++        int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++        if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks;
++        if (this.age != -32768) this.age += elapsedTicks;
++        this.lastTick = MinecraftServer.currentTick;
++        // CraftBukkit end
++
++        if (!this.level().isClientSide && this.age >= this.level().spigotConfig.itemDespawnRate) { // Spigot
++            // CraftBukkit start - fire ItemDespawnEvent
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
++                this.age = 0;
++                return;
++            }
++            // CraftBukkit end
++            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          }
-@@ -229,7 +248,10 @@
+     }
++    // Spigot end
+ 
+     @Override
+     public BlockPos getBlockPosBelowThatAffectsMyMovement() {
+@@ -229,7 +270,10 @@
  
      private void mergeWithNeighbours() {
          if (this.isMergable()) {
@@ -123,7 +149,7 @@
                  return entityitem != this && entityitem.isMergable();
              });
              Iterator iterator = list.iterator();
-@@ -259,7 +281,7 @@
+@@ -259,7 +303,7 @@
          ItemStack itemstack1 = other.getItem();
  
          if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) {
@@ -132,7 +158,7 @@
                  ItemEntity.merge(this, itemstack, other, itemstack1);
              } else {
                  ItemEntity.merge(other, itemstack1, this, itemstack);
-@@ -287,11 +309,16 @@
+@@ -287,11 +331,16 @@
      }
  
      private static void merge(ItemEntity targetEntity, ItemStack targetStack, ItemEntity sourceEntity, ItemStack sourceStack) {
@@ -150,7 +176,7 @@
          }
  
      }
-@@ -320,12 +347,17 @@
+@@ -320,12 +369,17 @@
          } else if (!this.getItem().canBeHurtBy(source)) {
              return false;
          } else {
@@ -169,7 +195,7 @@
              }
  
              return true;
-@@ -382,22 +414,62 @@
+@@ -382,22 +436,62 @@
          }
  
          if (this.getItem().isEmpty()) {
@@ -186,7 +212,7 @@
              ItemStack itemstack = this.getItem();
              Item item = itemstack.getItem();
              int i = itemstack.getCount();
-+
+ 
 +            // CraftBukkit start - fire PlayerPickupItemEvent
 +            int canHold = player.getInventory().canHold(itemstack);
 +            int remaining = i - canHold;
@@ -201,7 +227,7 @@
 +                    itemstack.setCount(i); // SPIGOT-5294 - restore count
 +                    return;
 +                }
- 
++
 +                // Call newer event afterwards
 +                EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                entityEvent.setCancelled(!entityEvent.getEntity().getCanPickupItems());
@@ -235,7 +261,7 @@
                      itemstack.setCount(i);
                  }
  
-@@ -492,7 +564,7 @@
+@@ -492,7 +586,7 @@
  
      public void makeFakeItem() {
          this.setNeverPickUp();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
index 6d6fcf7eac..dbf21d8f36 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
@@ -24,7 +24,26 @@
      }
  
      @Override
-@@ -235,7 +243,7 @@
+@@ -216,7 +224,18 @@
+         return this.assignProfessionWhenSpawned;
+     }
+ 
++    // Spigot Start
+     @Override
++    public void inactiveTick() {
++        // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
++        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
++            this.customServerAiStep((ServerLevel) this.level());
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
++    @Override
+     protected void customServerAiStep(ServerLevel world) {
+         ProfilerFiller gameprofilerfiller = Profiler.get();
+ 
+@@ -235,7 +254,7 @@
                      this.increaseProfessionLevelOnUpdate = false;
                  }
  
@@ -33,7 +52,7 @@
              }
          }
  
-@@ -360,7 +368,13 @@
+@@ -360,7 +379,13 @@
          while (iterator.hasNext()) {
              MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
  
@@ -48,7 +67,7 @@
          }
  
          this.resendOffersToTradingPlayer();
-@@ -429,7 +443,13 @@
+@@ -429,7 +454,13 @@
              while (iterator.hasNext()) {
                  MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
  
@@ -63,7 +82,7 @@
              }
          }
  
-@@ -489,7 +509,7 @@
+@@ -489,7 +520,7 @@
      @Override
      public void addAdditionalSaveData(CompoundTag nbt) {
          super.addAdditionalSaveData(nbt);
@@ -72,7 +91,7 @@
          Logger logger = Villager.LOGGER;
  
          Objects.requireNonNull(logger);
-@@ -512,7 +532,7 @@
+@@ -512,7 +543,7 @@
      public void readAdditionalSaveData(CompoundTag nbt) {
          super.readAdditionalSaveData(nbt);
          if (nbt.contains("VillagerData", 10)) {
@@ -81,7 +100,7 @@
              Logger logger = Villager.LOGGER;
  
              Objects.requireNonNull(logger);
-@@ -808,7 +828,7 @@
+@@ -808,7 +839,7 @@
                  entitywitch1.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null);
                  entitywitch1.setPersistenceRequired();
                  this.releaseAllPois();
@@ -90,7 +109,7 @@
  
              if (entitywitch == null) {
                  super.thunderHit(world, lightning);
-@@ -906,7 +926,7 @@
+@@ -906,7 +937,7 @@
              }).limit(5L).toList();
  
              if (list1.size() >= requiredCount) {
@@ -99,7 +118,7 @@
                      list.forEach(GolemSensor::golemDetected);
                  }
              }
-@@ -963,7 +983,7 @@
+@@ -963,7 +994,7 @@
      @Override
      public void startSleeping(BlockPos pos) {
          super.startSleeping(pos);
@@ -108,7 +127,7 @@
          this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);
          this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
      }
-@@ -971,7 +991,7 @@
+@@ -971,7 +1002,7 @@
      @Override
      public void stopSleeping() {
          super.stopSleeping();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
index 283001e049..26c18f6e4c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -19,7 +19,26 @@
  
  public abstract class AbstractArrow extends Projectile {
  
-@@ -88,23 +93,30 @@
+@@ -78,6 +83,18 @@
+     @Nullable
+     public ItemStack firedFromWeapon;
+ 
++    // Spigot Start
++    @Override
++    public void inactiveTick()
++    {
++        if ( this.isInGround() )
++        {
++            this.life += 1;
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
+     protected AbstractArrow(EntityType<? extends AbstractArrow> type, Level world) {
+         super(type, world);
+         this.pickup = AbstractArrow.Pickup.DISALLOWED;
+@@ -88,23 +105,30 @@
      }
  
      protected AbstractArrow(EntityType<? extends AbstractArrow> type, double x, double y, double z, Level world, ItemStack stack, @Nullable ItemStack weapon) {
@@ -30,7 +49,7 @@
 +        // CraftBukkit start - handle the owner before the rest of things
 +        this(type, x, y, z, world, stack, weapon, null);
 +    }
- 
++
 +    protected AbstractArrow(EntityType<? extends AbstractArrow> entitytypes, double d0, double d1, double d2, Level world, ItemStack itemstack, @Nullable ItemStack itemstack1, @Nullable LivingEntity ownerEntity) {
 +        this(entitytypes, world);
 +        this.setOwner(ownerEntity);
@@ -38,7 +57,7 @@
 +        this.pickupItemStack = itemstack.copy();
 +        this.setCustomName((Component) itemstack.get(DataComponents.CUSTOM_NAME));
 +        Unit unit = (Unit) itemstack.remove(DataComponents.INTANGIBLE_PROJECTILE);
-+
+ 
          if (unit != null) {
              this.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
          }
@@ -59,7 +78,7 @@
  
              if (i > 0) {
                  this.setPierceLevel((byte) i);
-@@ -114,8 +126,8 @@
+@@ -114,8 +138,8 @@
      }
  
      protected AbstractArrow(EntityType<? extends AbstractArrow> type, LivingEntity owner, Level world, ItemStack stack, @Nullable ItemStack shotFrom) {
@@ -70,7 +89,7 @@
      }
  
      public void setSoundEvent(SoundEvent sound) {
-@@ -282,7 +294,7 @@
+@@ -282,7 +306,7 @@
  
                  if (movingobjectpositionentity == null) {
                      if (this.isAlive() && blockHitResult.getType() != HitResult.Type.MISS) {
@@ -79,7 +98,7 @@
                          this.hasImpulse = true;
                      }
                  } else {
-@@ -290,7 +302,7 @@
+@@ -290,7 +314,7 @@
                          continue;
                      }
  
@@ -88,7 +107,7 @@
  
                      this.hasImpulse = true;
                      if (this.getPierceLevel() > 0 && projectiledeflection == ProjectileDeflection.NONE) {
-@@ -357,7 +369,7 @@
+@@ -357,7 +381,7 @@
      protected void tickDespawn() {
          ++this.life;
          if (this.life >= 1200) {
@@ -97,7 +116,7 @@
          }
  
      }
-@@ -423,7 +435,7 @@
+@@ -423,7 +447,7 @@
              }
  
              if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
@@ -106,7 +125,7 @@
                  return;
              }
  
-@@ -444,7 +456,13 @@
+@@ -444,7 +468,13 @@
          int k = entity.getRemainingFireTicks();
  
          if (this.isOnFire() && !flag) {
@@ -121,7 +140,7 @@
          }
  
          if (entity.hurtOrSimulate(damagesource, (float) i)) {
-@@ -490,7 +508,7 @@
+@@ -490,7 +520,7 @@
  
              this.playSound(this.soundEvent, 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
              if (this.getPierceLevel() <= 0) {
@@ -130,7 +149,7 @@
              }
          } else {
              entity.setRemainingFireTicks(k);
-@@ -506,7 +524,7 @@
+@@ -506,7 +536,7 @@
                          this.spawnAtLocation(worldserver2, this.getPickupItem(), 0.1F);
                      }
  
@@ -139,7 +158,7 @@
                  }
              }
          }
-@@ -675,7 +693,7 @@
+@@ -675,7 +705,7 @@
          }
  
          if (nbt.contains("weapon", 10)) {
@@ -148,7 +167,7 @@
          } else {
              this.firedFromWeapon = null;
          }
-@@ -688,34 +706,31 @@
+@@ -688,34 +718,31 @@
          Entity entity1 = entity;
          byte b0 = 0;
  
@@ -195,7 +214,7 @@
          }
  
          this.pickup = entityarrow_pickupstatus;
-@@ -724,9 +739,24 @@
+@@ -724,9 +751,24 @@
      @Override
      public void playerTouch(Player player) {
          if (!this.level().isClientSide && (this.isInGround() || this.isNoPhysics()) && this.shakeTime <= 0) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
index aa8f8c19cf..70b0990cf3 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
@@ -10,7 +10,37 @@
  
  public class FireworkRocketEntity extends Projectile implements ItemSupplier {
  
-@@ -152,7 +155,7 @@
+@@ -84,7 +87,29 @@
+         this.setOwner(entity);
+     }
+ 
++    // Spigot Start - copied from tick
+     @Override
++    public void inactiveTick() {
++        this.life += 1;
++
++        if (this.life > this.lifetime) {
++            Level world = this.level();
++
++            if (world instanceof ServerLevel) {
++                ServerLevel worldserver = (ServerLevel) world;
++
++                // CraftBukkit start
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                    this.explode(worldserver);
++                }
++                // CraftBukkit end
++            }
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
++    @Override
+     protected void defineSynchedData(SynchedEntityData.Builder builder) {
+         builder.define(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, FireworkRocketEntity.getDefaultItem());
+         builder.define(FireworkRocketEntity.DATA_ATTACHED_TO_TARGET, OptionalInt.empty());
+@@ -152,7 +177,7 @@
          }
  
          if (!this.noPhysics && this.isAlive() && movingobjectposition.getType() != HitResult.Type.MISS) {
@@ -19,7 +49,7 @@
              this.hasImpulse = true;
          }
  
-@@ -172,7 +175,11 @@
+@@ -172,7 +197,11 @@
              if (world instanceof ServerLevel) {
                  ServerLevel worldserver = (ServerLevel) world;
  
@@ -32,7 +62,7 @@
              }
          }
  
-@@ -182,7 +189,7 @@
+@@ -182,7 +211,7 @@
          world.broadcastEntityEvent(this, (byte) 17);
          this.gameEvent(GameEvent.EXPLODE, this.getOwner());
          this.dealExplosionDamage(world);
@@ -41,7 +71,7 @@
      }
  
      @Override
-@@ -191,7 +198,11 @@
+@@ -191,7 +220,11 @@
          Level world = this.level();
  
          if (world instanceof ServerLevel worldserver) {
@@ -54,7 +84,7 @@
          }
  
      }
-@@ -205,7 +216,11 @@
+@@ -205,7 +238,11 @@
  
          if (world instanceof ServerLevel worldserver) {
              if (this.hasExplosion()) {
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/paper-server/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
index 8b19596e77..b0ffa23faf 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
@@ -39,6 +39,9 @@ public class SpigotTimings {
 
     public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand");
 
+    public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck");
+    public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive");
+
     public static final HashMap<String, CustomTimingsHandler> entityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
     public static final HashMap<String, CustomTimingsHandler> tileEntityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
     public static final HashMap<String, CustomTimingsHandler> pluginTaskTimingMap = new HashMap<String, CustomTimingsHandler>();
diff --git a/paper-server/src/main/java/org/spigotmc/ActivationRange.java b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
new file mode 100644
index 0000000000..fb83cadf38
--- /dev/null
+++ b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
@@ -0,0 +1,263 @@
+package org.spigotmc;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ambient.AmbientCreature;
+import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.boss.EnderDragonPart;
+import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.boss.wither.WitherBoss;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.item.PrimedTnt;
+import net.minecraft.world.entity.monster.Creeper;
+import net.minecraft.world.entity.monster.Monster;
+import net.minecraft.world.entity.monster.Slime;
+import net.minecraft.world.entity.npc.Villager;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.projectile.AbstractArrow;
+import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
+import net.minecraft.world.entity.projectile.FireworkRocketEntity;
+import net.minecraft.world.entity.projectile.ThrowableProjectile;
+import net.minecraft.world.entity.projectile.ThrownTrident;
+import net.minecraft.world.entity.raid.Raider;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.craftbukkit.SpigotTimings;
+
+public class ActivationRange
+{
+
+    public enum ActivationType
+    {
+        MONSTER,
+        ANIMAL,
+        RAIDER,
+        MISC;
+
+        AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 );
+    }
+
+    static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 );
+
+    /**
+     * Initializes an entities type on construction to specify what group this
+     * entity is in for activation ranges.
+     *
+     * @param entity
+     * @return group id
+     */
+    public static ActivationType initializeEntityActivationType(Entity entity)
+    {
+        if ( entity instanceof Raider )
+        {
+            return ActivationType.RAIDER;
+        } else if ( entity instanceof Monster || entity instanceof Slime )
+        {
+            return ActivationType.MONSTER;
+        } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature )
+        {
+            return ActivationType.ANIMAL;
+        } else
+        {
+            return ActivationType.MISC;
+        }
+    }
+
+    /**
+     * These entities are excluded from Activation range checks.
+     *
+     * @param entity
+     * @param config
+     * @return boolean If it should always tick.
+     */
+    public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
+    {
+        if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 )
+                || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 )
+                || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 )
+                || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 )
+                || entity instanceof Player
+                || entity instanceof ThrowableProjectile
+                || entity instanceof EnderDragon
+                || entity instanceof EnderDragonPart
+                || entity instanceof WitherBoss
+                || entity instanceof AbstractHurtingProjectile
+                || entity instanceof LightningBolt
+                || entity instanceof PrimedTnt
+                || entity instanceof EndCrystal
+                || entity instanceof FireworkRocketEntity
+                || entity instanceof ThrownTrident )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Find what entities are in range of the players in the world and set
+     * active if in range.
+     *
+     * @param world
+     */
+    public static void activateEntities(Level world)
+    {
+        SpigotTimings.entityActivationCheckTimer.startTiming();
+        final int miscActivationRange = world.spigotConfig.miscActivationRange;
+        final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
+        final int animalActivationRange = world.spigotConfig.animalActivationRange;
+        final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
+
+        int maxRange = Math.max( monsterActivationRange, animalActivationRange );
+        maxRange = Math.max( maxRange, raiderActivationRange );
+        maxRange = Math.max( maxRange, miscActivationRange );
+        maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange );
+
+        for ( Player player : world.players() )
+        {
+            player.activatedTick = MinecraftServer.currentTick;
+            if ( world.spigotConfig.ignoreSpectatorActivation && player.isSpectator() )
+            {
+                continue;
+            }
+
+            ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange );
+            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange );
+            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange );
+            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange );
+            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange );
+
+            world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity);
+        }
+        SpigotTimings.entityActivationCheckTimer.stopTiming();
+    }
+
+    /**
+     * Checks for the activation state of all entities in this chunk.
+     *
+     * @param chunk
+     */
+    private static void activateEntity(Entity entity)
+    {
+        if ( MinecraftServer.currentTick > entity.activatedTick )
+        {
+            if ( entity.defaultActivationState )
+            {
+                entity.activatedTick = MinecraftServer.currentTick;
+                return;
+            }
+            if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) )
+            {
+                entity.activatedTick = MinecraftServer.currentTick;
+            }
+        }
+    }
+
+    /**
+     * If an entity is not in range, do some more checks to see if we should
+     * give it a shot.
+     *
+     * @param entity
+     * @return
+     */
+    public static boolean checkEntityImmunities(Entity entity)
+    {
+        // quick checks.
+        if ( entity.wasTouchingWater || entity.getRemainingFireTicks() > 0 )
+        {
+            return true;
+        }
+        if ( !( entity instanceof AbstractArrow ) )
+        {
+            if ( !entity.onGround() || !entity.passengers.isEmpty() || entity.isPassenger() )
+            {
+                return true;
+            }
+        } else if ( !( (AbstractArrow) entity ).isInGround() )
+        {
+            return true;
+        }
+        // special cases.
+        if ( entity instanceof LivingEntity )
+        {
+            LivingEntity living = (LivingEntity) entity;
+            if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 )
+            {
+                return true;
+            }
+            if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null )
+            {
+                return true;
+            }
+            if ( entity instanceof Villager && ( (Villager) entity ).canBreed() )
+            {
+                return true;
+            }
+            if ( entity instanceof Animal )
+            {
+                Animal animal = (Animal) entity;
+                if ( animal.isBaby() || animal.isInLove() )
+                {
+                    return true;
+                }
+                if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() )
+                {
+                    return true;
+                }
+            }
+            if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive
+                return true;
+            }
+        }
+        // SPIGOT-6644: Otherwise the target refresh tick will be missed
+        if (entity instanceof ExperienceOrb) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the entity is active for this tick.
+     *
+     * @param entity
+     * @return
+     */
+    public static boolean checkIfActive(Entity entity)
+    {
+        SpigotTimings.checkIfActiveTimer.startTiming();
+        // Never safe to skip fireworks or item gravity
+        if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId() + 1) % 4 == 0)) {
+            SpigotTimings.checkIfActiveTimer.stopTiming();
+            return true;
+        }
+
+        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
+
+        // Should this entity tick?
+        if ( !isActive )
+        {
+            if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
+            {
+                // Check immunities every 20 ticks.
+                if ( ActivationRange.checkEntityImmunities( entity ) )
+                {
+                    // Triggered some sort of immunity, give 20 full ticks before we check again.
+                    entity.activatedTick = MinecraftServer.currentTick + 20;
+                }
+                isActive = true;
+            }
+            // Add a little performance juice to active entities. Skip 1/4 if not immune.
+        } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) )
+        {
+            isActive = false;
+        }
+        SpigotTimings.checkIfActiveTimer.stopTiming();
+        return isActive;
+    }
+}
diff --git a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 7d1d2a4d32..da22dbeaad 100644
--- a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -194,4 +194,21 @@ public class SpigotWorldConfig
         this.itemDespawnRate = this.getInt( "item-despawn-rate", 6000 );
         this.log( "Item Despawn Rate: " + this.itemDespawnRate );
     }
+
+    public int animalActivationRange = 32;
+    public int monsterActivationRange = 32;
+    public int raiderActivationRange = 48;
+    public int miscActivationRange = 16;
+    public boolean tickInactiveVillagers = true;
+    public boolean ignoreSpectatorActivation = false;
+    private void activationRange()
+    {
+        this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange );
+        this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange );
+        this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange );
+        this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange );
+        this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers );
+        this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation );
+        this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation );
+    }
 }