--- a/net/minecraft/world/entity/animal/allay/Allay.java
+++ b/net/minecraft/world/entity/animal/allay/Allay.java
@@ -105,13 +_,14 @@
     private final DynamicGameEventListener<Allay.JukeboxListener> dynamicJukeboxListener;
     private final SimpleContainer inventory = new SimpleContainer(1);
     @Nullable
-    private BlockPos jukeboxPos;
-    private long duplicationCooldown = 0L;
+    public BlockPos jukeboxPos;
+    public long duplicationCooldown = 0L;
     private float holdingItemAnimationTicks;
     private float holdingItemAnimationTicks0;
     private float dancingAnimationTicks;
     private float spinningAnimationTicks;
     private float spinningAnimationTicks0;
+    public boolean forceDancing = false; // CraftBukkit
 
     public Allay(EntityType<? extends Allay> entityType, Level level) {
         super(entityType, level);
@@ -125,6 +_,12 @@
         );
     }
 
+    // CraftBukkit start
+    public void setCanDuplicate(boolean canDuplicate) {
+        this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate);
+    }
+    // CraftBukkit end
+
     @Override
     protected Brain.Provider<Allay> brainProvider() {
         return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -197,22 +_,22 @@
     }
 
     @Override
-    protected SoundEvent getAmbientSound() {
+    public SoundEvent getAmbientSound() {
         return this.hasItemInSlot(EquipmentSlot.MAINHAND) ? SoundEvents.ALLAY_AMBIENT_WITH_ITEM : SoundEvents.ALLAY_AMBIENT_WITHOUT_ITEM;
     }
 
     @Override
-    protected SoundEvent getHurtSound(DamageSource damageSource) {
+    public SoundEvent getHurtSound(DamageSource damageSource) {
         return SoundEvents.ALLAY_HURT;
     }
 
     @Override
-    protected SoundEvent getDeathSound() {
+    public SoundEvent getDeathSound() {
         return SoundEvents.ALLAY_DEATH;
     }
 
     @Override
-    protected float getSoundVolume() {
+    public float getSoundVolume() {
         return 0.4F;
     }
 
@@ -232,7 +_,7 @@
     public void aiStep() {
         super.aiStep();
         if (!this.level().isClientSide() && this.isAlive() && this.tickCount % 10 == 0) {
-            this.heal(1.0F);
+            this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
         }
 
         if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) {
@@ -300,7 +_,12 @@
         ItemStack itemInHand = player.getItemInHand(hand);
         ItemStack itemInHand1 = this.getItemInHand(InteractionHand.MAIN_HAND);
         if (this.isDancing() && itemInHand.is(ItemTags.DUPLICATES_ALLAYS) && this.canDuplicate()) {
-            this.duplicateAllay();
+            // CraftBukkit start - handle cancel duplication
+            Allay allay = this.duplicateAllay();
+            if (allay == null) {
+                return InteractionResult.SUCCESS;
+            }
+            // CraftBukkit end
             this.level().broadcastEntityEvent(this, (byte)18);
             this.level().playSound(player, this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F);
             this.removeInteractionItem(player, itemInHand);
@@ -399,6 +_,7 @@
     }
 
     private boolean shouldStopDancing() {
+        if (this.forceDancing) {return false;} // CraftBukkit
         return this.jukeboxPos == null
             || !this.jukeboxPos.closerToCenterThan(this.position(), GameEvent.JUKEBOX_PLAY.value().notificationRadius())
             || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX);
@@ -451,7 +_,7 @@
         super.readAdditionalSaveData(input);
         this.readInventoryFromTag(input);
         this.vibrationData = input.read("listener", VibrationSystem.Data.CODEC).orElseGet(VibrationSystem.Data::new);
-        this.setDuplicationCooldown(input.getIntOr("DuplicationCooldown", 0));
+        this.setDuplicationCooldown(input.getLongOr("DuplicationCooldown", 0)); // Paper - Load as long
     }
 
     @Override
@@ -470,22 +_,24 @@
         this.entityData.set(DATA_CAN_DUPLICATE, duplicationCooldown == 0L);
     }
 
-    private void duplicateAllay() {
+    @Nullable public Allay duplicateAllay() { // CraftBukkit - return allay
         Allay allay = EntityType.ALLAY.create(this.level(), EntitySpawnReason.BREEDING);
         if (allay != null) {
             allay.snapTo(this.position());
             allay.setPersistenceRequired();
             allay.resetDuplicationCooldown();
             this.resetDuplicationCooldown();
-            this.level().addFreshEntity(allay);
+            this.level().addFreshEntity(allay, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DUPLICATION); // CraftBukkit - reason for duplicated allay
         }
+
+        return allay; // CraftBukkit
     }
 
-    private void resetDuplicationCooldown() {
+    public void resetDuplicationCooldown() {
         this.setDuplicationCooldown(6000L);
     }
 
-    private boolean canDuplicate() {
+    public boolean canDuplicate() {
         return this.entityData.get(DATA_CAN_DUPLICATE);
     }
 
