--- a/net/minecraft/world/entity/monster/Zombie.java
+++ b/net/minecraft/world/entity/monster/Zombie.java
@@ -68,9 +_,7 @@
 
 public class Zombie extends Monster {
     private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby");
-    private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(
-        SPEED_MODIFIER_BABY_ID, 0.5, AttributeModifier.Operation.ADD_MULTIPLIED_BASE
-    );
+    private final AttributeModifier babyModifier = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, this.level().paperConfig().entities.behavior.babyZombieMovementModifier, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); // Paper - Make baby speed configurable
     private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
     private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(
         ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05F, AttributeModifier.Operation.ADD_VALUE
@@ -79,7 +_,7 @@
     private static final ResourceLocation ZOMBIE_RANDOM_SPAWN_BONUS_ID = ResourceLocation.withDefaultNamespace("zombie_random_spawn_bonus");
     private static final EntityDataAccessor<Boolean> DATA_BABY_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN);
     private static final EntityDataAccessor<Integer> DATA_SPECIAL_TYPE_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.INT);
-    private static final EntityDataAccessor<Boolean> DATA_DROWNED_CONVERSION_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN);
+    public static final EntityDataAccessor<Boolean> DATA_DROWNED_CONVERSION_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN);
     public static final float ZOMBIE_LEADER_CHANCE = 0.05F;
     public static final int REINFORCEMENT_ATTEMPTS = 50;
     public static final int REINFORCEMENT_RANGE_MAX = 40;
@@ -87,17 +_,19 @@
     private static final int NOT_CONVERTING = -1;
     private static final EntityDimensions BABY_DIMENSIONS = EntityType.ZOMBIE.getDimensions().scale(0.5F).withEyeHeight(0.93F);
     private static final float BREAK_DOOR_CHANCE = 0.1F;
-    private static final Predicate<Difficulty> DOOR_BREAKING_PREDICATE = difficulty -> difficulty == Difficulty.HARD;
+    public static final Predicate<Difficulty> DOOR_BREAKING_PREDICATE = difficulty -> difficulty == Difficulty.HARD;
     private static final boolean DEFAULT_BABY = false;
     private static final boolean DEFAULT_CAN_BREAK_DOORS = false;
     private static final int DEFAULT_IN_WATER_TIME = 0;
-    private final BreakDoorGoal breakDoorGoal = new BreakDoorGoal(this, DOOR_BREAKING_PREDICATE);
+    private final BreakDoorGoal breakDoorGoal; // Paper - move down
     private boolean canBreakDoors = false;
     private int inWaterTime = 0;
-    private int conversionTime;
+    public int conversionTime;
+    private boolean shouldBurnInDay = true; // Paper - Add more Zombie API
 
     public Zombie(EntityType<? extends Zombie> entityType, Level level) {
         super(entityType, level);
+        this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(entityType, level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty
     }
 
     public Zombie(Level level) {
@@ -106,7 +_,7 @@
 
     @Override
     protected void registerGoals() {
-        this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3));
+        if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3)); // Paper - Add zombie targets turtle egg config
         this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
         this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
         this.addBehaviourGoals();
@@ -118,7 +_,7 @@
         this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
         this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
-        this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
+        if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
         this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
         this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
     }
@@ -172,11 +_,16 @@
 
     @Override
     protected int getBaseExperienceReward(ServerLevel level) {
+        final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward
         if (this.isBaby()) {
             this.xpReward = (int)(this.xpReward * 2.5);
         }
 
-        return super.getBaseExperienceReward(level);
+        // Paper start - store previous value to reset after calculating XP reward
+        int reward = super.getBaseExperienceReward(level);
+        this.xpReward = previousReward;
+        return reward;
+        // Paper end - store previous value to reset after calculating XP reward
     }
 
     @Override
@@ -184,9 +_,9 @@
         this.getEntityData().set(DATA_BABY_ID, childZombie);
         if (this.level() != null && !this.level().isClientSide) {
             AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
-            attribute.removeModifier(SPEED_MODIFIER_BABY_ID);
+            attribute.removeModifier(this.babyModifier.id()); // Paper - Make baby speed configurable
             if (childZombie) {
-                attribute.addTransientModifier(SPEED_MODIFIER_BABY);
+                attribute.addTransientModifier(this.babyModifier); // Paper - Make baby speed configurable
             }
         }
     }
@@ -255,7 +_,14 @@
         super.aiStep();
     }
 
-    private void startUnderWaterConversion(int conversionTime) {
+    // Paper start - Add more Zombie API
+    public void stopDrowning() {
+        this.conversionTime = -1;
+        this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, false);
+    }
+    // Paper end - Add more Zombie API
+
+    public void startUnderWaterConversion(int conversionTime) {
         this.conversionTime = conversionTime;
         this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, true);
     }
@@ -268,31 +_,50 @@
     }
 
     protected void convertToZombieType(EntityType<? extends Zombie> entityType) {
-        this.convertTo(
+        Zombie converted = this.convertTo( // CraftBukkit
             entityType,
             ConversionParams.single(this, true, true),
-            zombie -> zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier())
-        );
+            // CraftBukkit start
+            zombie -> { zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier()); },
+            org.bukkit.event.entity.EntityTransformEvent.TransformReason.DROWNED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DROWNED);
+        if (converted == null) {
+            ((org.bukkit.entity.Zombie) this.getBukkitEntity()).setConversionTime(-1); // CraftBukkit - SPIGOT-5208: End conversion to stop event spam
+        }
+        // CraftBukkit end
     }
 
     @VisibleForTesting
     public boolean convertVillagerToZombieVillager(ServerLevel level, Villager villager) {
+        // CraftBukkit start
+        return convertVillagerToZombieVillager(level, villager, this.blockPosition(), this.isSilent(), org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.INFECTION) != null;
+    }
+
+    public static @Nullable ZombieVillager convertVillagerToZombieVillager(ServerLevel level, Villager villager, net.minecraft.core.BlockPos blockPosition, boolean silent, org.bukkit.event.entity.EntityTransformEvent.TransformReason transformReason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
+        // CraftBukkit end
         ZombieVillager zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), mob -> {
             mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
             mob.setVillagerData(villager.getVillagerData());
             mob.setGossips(villager.getGossips().copy());
             mob.setTradeOffers(villager.getOffers().copy());
             mob.setVillagerXp(villager.getVillagerXp());
-            if (!this.isSilent()) {
-                level.levelEvent(null, 1026, this.blockPosition(), 0);
+            // CraftBukkit start
+            if (!silent) {
+                level.levelEvent(null, 1026, blockPosition, 0);
             }
-        });
-        return zombieVillager != null;
-    }
-
-    protected boolean isSunSensitive() {
-        return true;
-    }
+        }, transformReason, creatureSpawnReason);
+        return zombieVillager;
+        // CraftBukkit end
+    }
+
+    public boolean isSunSensitive() {
+        return this.shouldBurnInDay; // Paper - Add more Zombie API
+    }
+
+    // Paper start - Add more Zombie API
+    public void setShouldBurnInDay(boolean shouldBurnInDay) {
+        this.shouldBurnInDay = shouldBurnInDay;
+    }
+    // Paper end - Add more Zombie API
 
     @Override
     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
@@ -325,13 +_,13 @@
                     if (SpawnPlacements.isSpawnPositionOk(type, level, blockPos)
                         && SpawnPlacements.checkSpawnRules(type, level, EntitySpawnReason.REINFORCEMENT, blockPos, level.random)) {
                         zombie.setPos(i1, i2, i3);
-                        if (!level.hasNearbyAlivePlayer(i1, i2, i3, 7.0)
+                        if (!level.hasNearbyAlivePlayerThatAffectsSpawning(i1, i2, i3, 7.0) // Paper - affects spawning api
                             && level.isUnobstructed(zombie)
                             && level.noCollision(zombie)
                             && (zombie.canSpawnInLiquids() || !level.containsAnyLiquid(zombie.getBoundingBox()))) {
-                            zombie.setTarget(target);
+                            zombie.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET); // CraftBukkit
                             zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, null);
-                            level.addFreshEntityWithPassengers(zombie);
+                            level.addFreshEntityWithPassengers(zombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
                             AttributeInstance attribute = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE);
                             AttributeModifier modifier = attribute.getModifier(REINFORCEMENT_CALLER_CHARGE_ID);
                             double d = modifier != null ? modifier.amount() : 0.0;
@@ -356,7 +_,12 @@
         if (flag) {
             float effectiveDifficulty = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
             if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < effectiveDifficulty * 0.3F) {
-                source.igniteForSeconds(2 * (int)effectiveDifficulty);
+                // CraftBukkit start
+                org.bukkit.event.entity.EntityCombustByEntityEvent event = new org.bukkit.event.entity.EntityCombustByEntityEvent(this.getBukkitEntity(), source.getBukkitEntity(), (float) (2 * (int)effectiveDifficulty));
+                if (event.callEvent()) {
+                    source.igniteForSeconds(event.getDuration(), false);
+                }
+                // CraftBukkit end
             }
         }
 
@@ -364,17 +_,17 @@
     }
 
     @Override
-    protected SoundEvent getAmbientSound() {
+    public SoundEvent getAmbientSound() {
         return SoundEvents.ZOMBIE_AMBIENT;
     }
 
     @Override
-    protected SoundEvent getHurtSound(DamageSource damageSource) {
+    public SoundEvent getHurtSound(DamageSource damageSource) {
         return SoundEvents.ZOMBIE_HURT;
     }
 
     @Override
-    protected SoundEvent getDeathSound() {
+    public SoundEvent getDeathSound() {
         return SoundEvents.ZOMBIE_DEATH;
     }
 
@@ -416,6 +_,7 @@
         compound.putBoolean("CanBreakDoors", this.canBreakDoors());
         compound.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
         compound.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
+        compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API
     }
 
     @Override
@@ -430,13 +_,15 @@
         } else {
             this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, false);
         }
+        this.shouldBurnInDay = compound.getBooleanOr("Paper.ShouldBurnInDay", true); // Paper - Add more Zombie API
     }
 
     @Override
     public boolean killedEntity(ServerLevel level, LivingEntity entity) {
         boolean flag = super.killedEntity(level, entity);
-        if ((level.getDifficulty() == Difficulty.NORMAL || level.getDifficulty() == Difficulty.HARD) && entity instanceof Villager villager) {
-            if (level.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
+        final double fallbackChance = level.getDifficulty() == Difficulty.HARD ? 100 : level.getDifficulty() == Difficulty.NORMAL ? 50 : 0; // Paper - Configurable chance of villager zombie infection - moved up from belows if
+        if (this.random.nextDouble() * 100 < level.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && entity instanceof Villager villager) { // Paper - Configurable chance of villager zombie infection
+            if (false && level.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { // Paper - Configurable chance of villager zombie infection - moved to "fallbackChance"
                 return flag;
             }
 
@@ -472,7 +_,7 @@
         spawnGroupData = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
         float specialMultiplier = difficulty.getSpecialMultiplier();
         if (spawnReason != EntitySpawnReason.CONVERSION) {
-            this.setCanPickUpLoot(random.nextFloat() < 0.55F * specialMultiplier);
+            this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || random.nextFloat() < 0.55F * specialMultiplier); // Paper - Add world settings for mobs picking up loot
         }
 
         if (spawnGroupData == null) {
@@ -499,7 +_,7 @@
                             chicken1.finalizeSpawn(level, difficulty, EntitySpawnReason.JOCKEY, null);
                             chicken1.setChickenJockey(true);
                             this.startRiding(chicken1);
-                            level.addFreshEntity(chicken1);
+                            level.addFreshEntity(chicken1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
                         }
                     }
                 }
