--- a/net/minecraft/world/entity/Leashable.java
+++ b/net/minecraft/world/entity/Leashable.java
@@ -80,6 +_,11 @@
     }
 
     default void writeLeashData(ValueOutput output, @Nullable Leashable.LeashData leashData) {
+        // CraftBukkit start - SPIGOT-7487: Don't save (and possible drop) leash, when the holder was removed by a plugin
+        if (leashData != null && leashData.leashHolder != null && leashData.leashHolder.pluginRemoved) {
+            return;
+        }
+        // CraftBukkit end
         output.storeNullable("leash", Leashable.LeashData.CODEC, leashData);
     }
 
@@ -99,7 +_,9 @@
             }
 
             if (entity.tickCount > 100) {
+                entity.forceDrops = true; // CraftBukkit
                 entity.spawnAtLocation(serverLevel, Items.LEAD);
+                entity.forceDrops = false; // CraftBukkit
                 entity.setLeashData(null);
             }
         }
@@ -123,7 +_,9 @@
             entity.onLeashRemoved();
             if (entity.level() instanceof ServerLevel serverLevel) {
                 if (dropItem) {
+                    entity.forceDrops = true; // CraftBukkit
                     entity.spawnAtLocation(serverLevel, Items.LEAD);
+                    entity.forceDrops = false; // CraftBukkit
                 }
 
                 if (broadcastPacket) {
@@ -143,7 +_,15 @@
 
         if (leashData != null && leashData.leashHolder != null) {
             if (!entity.canInteractWithLevel() || !leashData.leashHolder.canInteractWithLevel()) {
-                if (level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                // Paper start - Expand EntityUnleashEvent
+                final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(
+                    entity.getBukkitEntity(),
+                    !entity.isAlive() ? org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH : org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.HOLDER_GONE,
+                    level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved
+                );
+                event.callEvent();
+                if (event.isDropLeash()) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
+                    // Paper end - Expand EntityUnleashEvent
                     entity.dropLeash();
                 } else {
                     entity.removeLeash();
@@ -154,8 +_,7 @@
             if (leashHolder != null && leashHolder.level() == entity.level()) {
                 double d = entity.leashDistanceTo(leashHolder);
                 entity.whenLeashedTo(leashHolder);
-                if (d > entity.leashSnapDistance()) {
-                    level.playSound(null, leashHolder.getX(), leashHolder.getY(), leashHolder.getZ(), SoundEvents.LEAD_BREAK, SoundSource.NEUTRAL, 1.0F, 1.0F);
+                if (d > entity.leashSnapDistanceOrConfig()) { // Paper - Configurable max leash distance
                     entity.leashTooFarBehaviour();
                 } else if (d > entity.leashElasticDistance() - leashHolder.getBbWidth() - entity.getBbWidth()
                     && entity.checkElasticInteractions(leashHolder, leashData)) {
@@ -175,6 +_,12 @@
         entity.checkFallDistanceAccumulation();
     }
 
+    // Paper start - Configurable max leash distance
+    default double leashSnapDistanceOrConfig() {
+        if (!(this instanceof final Entity entity)) return leashSnapDistance();
+        return entity.level().paperConfig().misc.maxLeashDistance.or(leashSnapDistance());
+    }
+    // Paper end - Configurable max leash distance
     default double leashSnapDistance() {
         return 12.0;
     }
@@ -196,7 +_,25 @@
     }
 
     default void leashTooFarBehaviour() {
-        this.dropLeash();
+        // CraftBukkit start
+        boolean dropLeash = true; // Paper
+        if (this instanceof Entity entity) {
+            // Paper start - Expand EntityUnleashEvent
+            final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(entity.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true);
+            if (!event.callEvent()) return;
+
+            Entity leashHolder = this.getLeashHolder();
+            Level level = leashHolder.level();
+            dropLeash = event.isDropLeash();
+            level.playSound(null, leashHolder.getX(), leashHolder.getY(), leashHolder.getZ(), SoundEvents.LEAD_BREAK, SoundSource.NEUTRAL, 1.0F, 1.0F); // Moved from Leashable#tickLeash
+        }
+        // CraftBukkit end
+        if (dropLeash) {
+            this.dropLeash();
+        } else {
+            this.removeLeash();
+        }
+        // Paper end - Expand EntityUnleashEvent
     }
 
     default void closeRangeLeashBehaviour(Entity entity) {
