--- a/net/minecraft/world/entity/animal/Animal.java
+++ b/net/minecraft/world/entity/animal/Animal.java
@@ -29,11 +29,20 @@
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.level.pathfinder.PathType;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityBreedEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityEnterLoveModeEvent;
+// CraftBukkit end
+
 public abstract class Animal extends AgeableMob {
+
     protected static final int PARENT_AGE_AFTER_BREEDING = 6000;
     public int inLove;
     @Nullable
     public UUID loveCause;
+    public ItemStack breedItem; // CraftBukkit - Add breedItem variable
 
     protected Animal(EntityType<? extends Animal> type, Level world) {
         super(type, world);
@@ -58,20 +67,28 @@
         }
 
         if (this.inLove > 0) {
-            this.inLove--;
+            --this.inLove;
             if (this.inLove % 10 == 0) {
-                double d = this.random.nextGaussian() * 0.02;
-                double e = this.random.nextGaussian() * 0.02;
-                double f = this.random.nextGaussian() * 0.02;
-                this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d, e, f);
+                double d0 = this.random.nextGaussian() * 0.02D;
+                double d1 = this.random.nextGaussian() * 0.02D;
+                double d2 = this.random.nextGaussian() * 0.02D;
+
+                this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), d0, d1, d2);
             }
         }
+
     }
 
     @Override
-    protected void actuallyHurt(DamageSource source, float amount) {
+    // CraftBukkit start - void -> boolean
+    public boolean actuallyHurt(DamageSource damagesource, float f, EntityDamageEvent event) {
+        boolean damageResult = super.actuallyHurt(damagesource, f, event);
+        if (!damageResult) {
+            return false;
+        }
         this.resetLove();
-        super.actuallyHurt(source, amount);
+        return true;
+        // CraftBukkit end
     }
 
     @Override
@@ -86,6 +103,7 @@
         if (this.loveCause != null) {
             nbt.putUUID("LoveCause", this.loveCause);
         }
+
     }
 
     @Override
@@ -95,11 +113,10 @@
         this.loveCause = nbt.hasUUID("LoveCause") ? nbt.getUUID("LoveCause") : null;
     }
 
-    public static boolean checkAnimalSpawnRules(
-        EntityType<? extends Animal> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random
-    ) {
-        boolean bl = MobSpawnType.ignoresLightRequirements(spawnReason) || isBrightEnoughToSpawn(world, pos);
-        return world.getBlockState(pos.below()).is(BlockTags.ANIMALS_SPAWNABLE_ON) && bl;
+    public static boolean checkAnimalSpawnRules(EntityType<? extends Animal> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
+        boolean flag = MobSpawnType.ignoresLightRequirements(spawnReason) || Animal.isBrightEnoughToSpawn(world, pos);
+
+        return world.getBlockState(pos.below()).is(BlockTags.ANIMALS_SPAWNABLE_ON) && flag;
     }
 
     protected static boolean isBrightEnoughToSpawn(BlockAndTintGetter world, BlockPos pos) {
@@ -125,17 +142,20 @@
 
     @Override
     public InteractionResult mobInteract(Player player, InteractionHand hand) {
-        ItemStack itemStack = player.getItemInHand(hand);
-        if (this.isFood(itemStack)) {
+        ItemStack itemstack = player.getItemInHand(hand);
+
+        if (this.isFood(itemstack)) {
             int i = this.getAge();
+
             if (!this.level().isClientSide && i == 0 && this.canFallInLove()) {
-                this.usePlayerItem(player, hand, itemStack);
-                this.setInLove(player);
+                final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying
+                this.usePlayerItem(player, hand, itemstack);
+                this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
                 return InteractionResult.SUCCESS;
             }
 
             if (this.isBaby()) {
-                this.usePlayerItem(player, hand, itemStack);
+                this.usePlayerItem(player, hand, itemstack);
                 this.ageUp(getSpeedUpSecondsWhenFeeding(-i), true);
                 return InteractionResult.sidedSuccess(this.level().isClientSide);
             }
@@ -156,13 +176,28 @@
         return this.inLove <= 0;
     }
 
+    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying
     public void setInLove(@Nullable Player player) {
-        this.inLove = 600;
+        // Paper start - Fix EntityBreedEvent copying
+        this.setInLove(player, null);
+    }
+    public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) {
+        if (breedItemCopy != null) this.breedItem = breedItemCopy;
+        // Paper end - Fix EntityBreedEvent copying
+        // CraftBukkit start
+        EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600);
+        if (entityEnterLoveModeEvent.isCancelled()) {
+            this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled
+            return;
+        }
+        this.inLove = entityEnterLoveModeEvent.getTicksInLove();
+        // CraftBukkit end
         if (player != null) {
             this.loveCause = player.getUUID();
         }
+        // Paper - Fix EntityBreedEvent copying; set breed item in better place
 
-        this.level().broadcastEntityEvent(this, (byte)18);
+        this.level().broadcastEntityEvent(this, (byte) 18);
     }
 
     public void setInLoveTime(int loveTicks) {
@@ -178,8 +213,9 @@
         if (this.loveCause == null) {
             return null;
         } else {
-            Player player = this.level().getPlayerByUUID(this.loveCause);
-            return player instanceof ServerPlayer ? (ServerPlayer)player : null;
+            Player entityhuman = this.level().getPlayerByUUID(this.loveCause);
+
+            return entityhuman instanceof ServerPlayer ? (ServerPlayer) entityhuman : null;
         }
     }
 
@@ -192,45 +228,74 @@
     }
 
     public boolean canMate(Animal other) {
-        return other != this && other.getClass() == this.getClass() && this.isInLove() && other.isInLove();
+        return other == this ? false : (other.getClass() != this.getClass() ? false : this.isInLove() && other.isInLove());
     }
 
     public void spawnChildFromBreeding(ServerLevel world, Animal other) {
-        AgeableMob ageableMob = this.getBreedOffspring(world, other);
-        if (ageableMob != null) {
-            ageableMob.setBaby(true);
-            ageableMob.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
-            this.finalizeSpawnChildFromBreeding(world, other, ageableMob);
-            world.addFreshEntityWithPassengers(ageableMob);
+        AgeableMob entityageable = this.getBreedOffspring(world, other);
+
+        if (entityageable != null) {
+            entityageable.setBaby(true);
+            entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+            // CraftBukkit start - call EntityBreedEvent
+            ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> {
+                return Optional.ofNullable(other.getLoveCause());
+            }).orElse(null);
+            int experience = this.getRandom().nextInt(7) + 1;
+            EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience);
+            if (entityBreedEvent.isCancelled()) {
+                return;
+            }
+            experience = entityBreedEvent.getExperience();
+            this.finalizeSpawnChildFromBreeding(world, other, entityageable, experience);
+            world.addFreshEntityWithPassengers(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
+            // CraftBukkit end
         }
     }
 
     public void finalizeSpawnChildFromBreeding(ServerLevel world, Animal other, @Nullable AgeableMob baby) {
-        Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(other.getLoveCause())).ifPresent(player -> {
-            player.awardStat(Stats.ANIMALS_BRED);
-            CriteriaTriggers.BRED_ANIMALS.trigger(player, this, other, baby);
-        });
+        // CraftBukkit start
+        this.finalizeSpawnChildFromBreeding(world, other, baby, this.getRandom().nextInt(7) + 1);
+    }
+
+    public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) {
+        // CraftBukkit end
+        // Paper start
+        ServerPlayer entityplayer = this.getLoveCause();
+        if (entityplayer == null) entityplayer = entityanimal.getLoveCause();
+        if (entityplayer != null) {
+            // Paper end
+            entityplayer.awardStat(Stats.ANIMALS_BRED);
+            CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable);
+        } // Paper
         this.setAge(6000);
-        other.setAge(6000);
+        entityanimal.setAge(6000);
         this.resetLove();
-        other.resetLove();
-        world.broadcastEntityEvent(this, (byte)18);
-        if (world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-            world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
+        entityanimal.resetLove();
+        worldserver.broadcastEntityEvent(this, (byte) 18);
+        if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+            // CraftBukkit start - use event experience
+            if (experience > 0) {
+                worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper
+            }
+            // CraftBukkit end
         }
+
     }
 
     @Override
     public void handleEntityEvent(byte status) {
         if (status == 18) {
-            for (int i = 0; i < 7; i++) {
-                double d = this.random.nextGaussian() * 0.02;
-                double e = this.random.nextGaussian() * 0.02;
-                double f = this.random.nextGaussian() * 0.02;
-                this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d, e, f);
+            for (int i = 0; i < 7; ++i) {
+                double d0 = this.random.nextGaussian() * 0.02D;
+                double d1 = this.random.nextGaussian() * 0.02D;
+                double d2 = this.random.nextGaussian() * 0.02D;
+
+                this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), d0, d1, d2);
             }
         } else {
             super.handleEntityEvent(status);
         }
+
     }
 }
