--- a/net/minecraft/world/entity/monster/Shulker.java
+++ b/net/minecraft/world/entity/monster/Shulker.java
@@ -66,7 +_,7 @@
     );
     protected static final EntityDataAccessor<Direction> DATA_ATTACH_FACE_ID = SynchedEntityData.defineId(Shulker.class, EntityDataSerializers.DIRECTION);
     protected static final EntityDataAccessor<Byte> DATA_PEEK_ID = SynchedEntityData.defineId(Shulker.class, EntityDataSerializers.BYTE);
-    protected static final EntityDataAccessor<Byte> DATA_COLOR_ID = SynchedEntityData.defineId(Shulker.class, EntityDataSerializers.BYTE);
+    public static final EntityDataAccessor<Byte> DATA_COLOR_ID = SynchedEntityData.defineId(Shulker.class, EntityDataSerializers.BYTE);
     private static final int TELEPORT_STEPS = 6;
     private static final byte NO_COLOR = 16;
     private static final byte DEFAULT_COLOR = 16;
@@ -116,7 +_,7 @@
     }
 
     @Override
-    protected SoundEvent getAmbientSound() {
+    public SoundEvent getAmbientSound() {
         return SoundEvents.SHULKER_AMBIENT;
     }
 
@@ -128,12 +_,12 @@
     }
 
     @Override
-    protected SoundEvent getDeathSound() {
+    public SoundEvent getDeathSound() {
         return SoundEvents.SHULKER_DEATH;
     }
 
     @Override
-    protected SoundEvent getHurtSound(DamageSource damageSource) {
+    public SoundEvent getHurtSound(DamageSource damageSource) {
         return this.isClosed() ? SoundEvents.SHULKER_HURT_CLOSED : SoundEvents.SHULKER_HURT;
     }
 
@@ -277,8 +_,10 @@
     }
 
     @Override
-    public void stopRiding() {
-        super.stopRiding();
+    // Paper start - Force entity dismount during teleportation
+    public void stopRiding(boolean suppressCancellation) {
+        super.stopRiding(suppressCancellation);
+        // Paper end - Force entity dismount during teleportation
         if (this.level().isClientSide()) {
             this.clientOldAttachPosition = this.blockPosition();
         }
@@ -391,6 +_,14 @@
                     && this.level().getWorldBorder().isWithinBounds(blockPos1)
                     && this.level().noCollision(this, new AABB(blockPos1).deflate(1.0E-6))) {
                     Direction direction = this.findAttachableSurface(blockPos1);
+                    // CraftBukkit start
+                    org.bukkit.event.entity.EntityTeleportEvent teleportEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, blockPos1.getX(), blockPos1.getY(), blockPos1.getZ());
+                    if (teleportEvent.isCancelled() || teleportEvent.getTo() == null) { // Paper
+                        return false;
+                    } else {
+                        blockPos1 = org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(teleportEvent.getTo());
+                    }
+                    // CraftBukkit end
                     if (direction != null) {
                         this.unRide();
                         this.setAttachFace(direction);
@@ -455,7 +_,12 @@
                 if (shulker != null) {
                     shulker.setVariant(this.getVariant());
                     shulker.snapTo(vec3);
-                    this.level().addFreshEntity(shulker);
+                    // Paper start - Call ShulkerDuplicateEvent
+                    if (!new io.papermc.paper.event.entity.ShulkerDuplicateEvent((org.bukkit.entity.Shulker) shulker.getBukkitEntity(), (org.bukkit.entity.Shulker) this.getBukkitEntity()).callEvent()) {
+                        return;
+                    }
+                    // Paper end - Call ShulkerDuplicateEvent
+                    this.level().addFreshEntity(shulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - the mysteries of life
                 }
             }
         }
@@ -470,7 +_,7 @@
         return this.entityData.get(DATA_ATTACH_FACE_ID);
     }
 
-    private void setAttachFace(Direction attachFace) {
+    public void setAttachFace(Direction attachFace) {
         this.entityData.set(DATA_ATTACH_FACE_ID, attachFace);
     }
 
@@ -483,11 +_,11 @@
         super.onSyncedDataUpdated(key);
     }
 
-    private int getRawPeekAmount() {
+    public int getRawPeekAmount() {
         return this.entityData.get(DATA_PEEK_ID);
     }
 
-    void setRawPeekAmount(int peekAmount) {
+    public void setRawPeekAmount(int peekAmount) {
         if (!this.level().isClientSide()) {
             this.getAttribute(Attributes.ARMOR).removeModifier(COVERED_ARMOR_MODIFIER_ID);
             if (peekAmount == 0) {
