From 9c1d7e8f87756ccf3f551945728ebf535eb63af6 Mon Sep 17 00:00:00 2001 From: Reuben George Date: Fri, 17 Oct 2025 09:52:22 +0530 Subject: [PATCH 1/6] Update ExprLeashHolder to use Leashable instead of LivingEntity --- .../java/ch/njol/skript/expressions/ExprLeashHolder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java index 7e223c2e3f7..797e2347791 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java @@ -1,7 +1,7 @@ package ch.njol.skript.expressions; +import io.papermc.paper.entity.Leashable; import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; import org.jetbrains.annotations.Nullable; import ch.njol.skript.doc.Description; @@ -14,15 +14,15 @@ @Description("The leash holder of a living entity.") @Examples("set {_example} to the leash holder of the target mob") @Since("2.3") -public class ExprLeashHolder extends SimplePropertyExpression { +public class ExprLeashHolder extends SimplePropertyExpression { static { - register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "livingentities"); + register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "leashables"); } @Override @Nullable - public Entity convert(LivingEntity entity) { + public Entity convert(Leashable entity) { return entity.isLeashed() ? entity.getLeashHolder() : null; } From f2a9b3e5fab9ec0b5a0dd874b269a64507bb0250 Mon Sep 17 00:00:00 2001 From: Reuben George Date: Fri, 17 Oct 2025 10:52:45 +0530 Subject: [PATCH 2/6] Update EffLeash and ExprLeashHolder to support all leashable entities --- .../java/ch/njol/skript/effects/EffLeash.java | 30 +++++++++++-------- .../skript/expressions/ExprLeashHolder.java | 6 ++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffLeash.java b/src/main/java/ch/njol/skript/effects/EffLeash.java index 4e1b0159b08..27006dc29ee 100644 --- a/src/main/java/ch/njol/skript/effects/EffLeash.java +++ b/src/main/java/ch/njol/skript/effects/EffLeash.java @@ -1,7 +1,7 @@ package ch.njol.skript.effects; +import io.papermc.paper.entity.Leashable; import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -17,8 +17,8 @@ @Name("Leash entities") @Description({ - "Leash living entities to other entities. When trying to leash an Ender Dragon, Wither, Player, or a Bat, this effect will not work.", - "See Spigot's Javadocs for more info." + "Leash entities to other entities. This works with all leashable entities including living entities and boats.", + "See Paper's Javadocs for more info." }) @Examples({ "on right click:", @@ -30,15 +30,15 @@ public class EffLeash extends Effect { static { Skript.registerEffect(EffLeash.class, - "(leash|lead) %livingentities% to %entity%", - "make %entity% (leash|lead) %livingentities%", - "un(leash|lead) [holder of] %livingentities%"); + "(leash|lead) %entities% to %entity%", + "make %entity% (leash|lead) %entities%", + "un(leash|lead) [holder of] %entities%"); } @SuppressWarnings("null") private Expression holder; @SuppressWarnings("null") - private Expression targets; + private Expression targets; private boolean leash; @Override @@ -47,9 +47,9 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye leash = matchedPattern != 2; if (leash) { holder = (Expression) exprs[1 - matchedPattern]; - targets = (Expression) exprs[matchedPattern]; + targets = (Expression) exprs[matchedPattern]; } else { - targets = (Expression) exprs[0]; + targets = (Expression) exprs[0]; } return true; } @@ -60,11 +60,15 @@ protected void execute(Event e) { Entity holder = this.holder.getSingle(e); if (holder == null) return; - for (LivingEntity target : targets.getArray(e)) - target.setLeashHolder(holder); + for (Entity target : targets.getArray(e)) { + if (target instanceof Leashable leashable) + leashable.setLeashHolder(holder); + } } else { - for (LivingEntity target : targets.getArray(e)) - target.setLeashHolder(null); + for (Entity target : targets.getArray(e)) { + if (target instanceof Leashable leashable) + leashable.setLeashHolder(null); + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java index 797e2347791..4730d07d05a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java @@ -11,13 +11,13 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; @Name("Leash Holder") -@Description("The leash holder of a living entity.") -@Examples("set {_example} to the leash holder of the target mob") +@Description("The leash holder of a leashable entity.") +@Examples("set {_example} to the leash holder of the target entity") @Since("2.3") public class ExprLeashHolder extends SimplePropertyExpression { static { - register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "leashables"); + register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "entities"); } @Override From 85313b18e4edd597bff1dd0f2778594af5d76ce7 Mon Sep 17 00:00:00 2001 From: Reuben George Date: Sat, 18 Oct 2025 20:18:16 +0530 Subject: [PATCH 3/6] Refactor ExprLeashHolder to use Entity type and add a check for leash holder --- .../java/ch/njol/skript/expressions/ExprLeashHolder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java index 4730d07d05a..e3bfa282bde 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java @@ -14,7 +14,7 @@ @Description("The leash holder of a leashable entity.") @Examples("set {_example} to the leash holder of the target entity") @Since("2.3") -public class ExprLeashHolder extends SimplePropertyExpression { +public class ExprLeashHolder extends SimplePropertyExpression { static { register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "entities"); @@ -22,8 +22,11 @@ public class ExprLeashHolder extends SimplePropertyExpression @Override @Nullable - public Entity convert(Leashable entity) { - return entity.isLeashed() ? entity.getLeashHolder() : null; + public Entity convert(Entity entity) { + if (entity instanceof Leashable leashable && leashable.isLeashed()) { + return leashable.getLeashHolder(); + } + return null; } @Override From b1408bd28be70280b66ba460826ac728262b294d Mon Sep 17 00:00:00 2001 From: Reuben George Date: Mon, 20 Oct 2025 20:48:50 +0530 Subject: [PATCH 4/6] Add tests for leashing and unleashing entities with various scenarios --- .../skript/tests/syntaxes/effects/EffLeash.sk | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/test/skript/tests/syntaxes/effects/EffLeash.sk diff --git a/src/test/skript/tests/syntaxes/effects/EffLeash.sk b/src/test/skript/tests/syntaxes/effects/EffLeash.sk new file mode 100644 index 00000000000..d19ffd2e724 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffLeash.sk @@ -0,0 +1,120 @@ +test "leashing entities": + # Test leashing a living entity (cow) + spawn a cow at test-location: + set {_cow} to entity + spawn a player at test-location: + set {_player} to entity + + # Leash the cow to the player + leash {_cow} to {_player} + assert {_cow} is leashed with "Cow should be leashed after leash effect" + assert leash holder of {_cow} is {_player} with "Cow's leash holder should be the player", expected {_player}, got leash holder of {_cow} + + # Test unleashing + unleash {_cow} + assert {_cow} is not leashed with "Cow should not be leashed after unleash effect" + assert leash holder of {_cow} is not set with "Cow's leash holder should not be set after unleashing" + + # Clean up + clear entity within {_cow} + clear entity within {_player} + +test "leashing boats": + # Test leashing a non-living entity (boat) + spawn a boat at test-location: + set {_boat} to entity + spawn a zombie at test-location: + set {_zombie} to entity + + # Leash the boat to the zombie + leash {_boat} to {_zombie} + assert {_boat} is leashed with "Boat should be leashed after leash effect" + assert leash holder of {_boat} is {_zombie} with "Boat's leash holder should be the zombie", expected {_zombie}, got leash holder of {_boat} + + # Test unleashing the boat + unleash {_boat} + assert {_boat} is not leashed with "Boat should not be leashed after unleash effect" + + # Clean up + clear entity within {_boat} + clear entity within {_zombie} + +test "leashing non-leashable entities": + # Test that non-leashable entities don't throw exceptions + spawn an armor stand at test-location: + set {_armorstand} to entity + spawn a pig at test-location: + set {_pig} to entity + + # Try to leash a non-leashable entity (should not throw exception) + leash {_armorstand} to {_pig} + assert {_armorstand} is not leashed with "Armor stand should not be leashed (not leashable)" + assert leash holder of {_armorstand} is not set with "Armor stand should have no leash holder" + + # Clean up + clear entity within {_armorstand} + clear entity within {_pig} + +test "leashing multiple entities": + # Test leashing multiple entities at once + spawn a sheep, a cow and a pig at test-location: + add entity to {_animals::*} + spawn a villager at test-location: + set {_holder} to entity + + # Leash all animals to the villager + leash {_animals::*} to {_holder} + + loop {_animals::*}: + assert loop-value is leashed with "Animal %loop-value% should be leashed" + assert leash holder of loop-value is {_holder} with "Animal's leash holder should be the villager" + + # Unleash all animals + unleash {_animals::*} + + loop {_animals::*}: + assert loop-value is not leashed with "Animal %loop-value% should not be leashed after unleashing" + + # Clean up + clear entities within {_animals::*} + clear entity within {_holder} + +test "alternative leash syntax": + # Test the alternative "make X leash Y" syntax + spawn a wolf at test-location: + set {_wolf} to entity + spawn a player at test-location: + set {_player} to entity + + # Use alternative syntax + make {_player} leash {_wolf} + assert {_wolf} is leashed with "Wolf should be leashed using alternative syntax" + assert leash holder of {_wolf} is {_player} with "Wolf's leash holder should be the player" + + # Clean up + unleash holder of {_wolf} + clear entity within {_wolf} + clear entity within {_player} + +test "leash holder returns null for non-leashable": + # Test that getting leash holder of non-leashable entities returns null without exception + spawn an ender crystal at test-location: + set {_crystal} to entity + + set {_holder} to leash holder of {_crystal} + assert {_holder} is not set with "Leash holder of non-leashable entity should be null" + + # Clean up + clear entity within {_crystal} + +test "leash holder returns null for unleashed entity": + # Test that getting leash holder of unleashed leashable entity returns null + spawn a horse at test-location: + set {_horse} to entity + + set {_holder} to leash holder of {_horse} + assert {_holder} is not set with "Leash holder of unleashed entity should be null" + + # Clean up + clear entity within {_horse} + From 112b506e98d1eb6e54ff6ed30505e9589cf450bc Mon Sep 17 00:00:00 2001 From: Reuben George Date: Tue, 21 Oct 2025 22:11:25 +0530 Subject: [PATCH 5/6] Update CondLeashed to support all entities and use Leashable interface --- .../ch/njol/skript/conditions/CondLeashed.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondLeashed.java b/src/main/java/ch/njol/skript/conditions/CondLeashed.java index f2fb1fa887d..cd4a171d73b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondLeashed.java +++ b/src/main/java/ch/njol/skript/conditions/CondLeashed.java @@ -5,21 +5,25 @@ import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import org.bukkit.entity.LivingEntity; +import io.papermc.paper.entity.Leashable; +import org.bukkit.entity.Entity; @Name("Is Leashed") @Description("Checks to see if an entity is currently leashed.") @Examples("target entity is leashed") @Since("2.5") -public class CondLeashed extends PropertyCondition { +public class CondLeashed extends PropertyCondition { static { - register(CondLeashed.class, PropertyType.BE, "leashed", "livingentities"); + register(CondLeashed.class, PropertyType.BE, "leashed", "entities"); } @Override - public boolean check(LivingEntity entity) { - return entity.isLeashed(); + public boolean check(Entity entity) { + if(entity instanceof Leashable leashable) { + return leashable.isLeashed(); + } + return false; } @Override From 3f719e35bb6fcfa9dc694b4fe54751056ae0e6c1 Mon Sep 17 00:00:00 2001 From: Reuben George Date: Tue, 21 Oct 2025 22:12:21 +0530 Subject: [PATCH 6/6] Refactor leashing tests, simplifying it to test for now --- .../skript/tests/syntaxes/effects/EffLeash.sk | 119 +----------------- 1 file changed, 5 insertions(+), 114 deletions(-) diff --git a/src/test/skript/tests/syntaxes/effects/EffLeash.sk b/src/test/skript/tests/syntaxes/effects/EffLeash.sk index d19ffd2e724..1273edc522e 100644 --- a/src/test/skript/tests/syntaxes/effects/EffLeash.sk +++ b/src/test/skript/tests/syntaxes/effects/EffLeash.sk @@ -1,120 +1,11 @@ -test "leashing entities": - # Test leashing a living entity (cow) +test "leash basic": spawn a cow at test-location: set {_cow} to entity - spawn a player at test-location: - set {_player} to entity - - # Leash the cow to the player - leash {_cow} to {_player} - assert {_cow} is leashed with "Cow should be leashed after leash effect" - assert leash holder of {_cow} is {_player} with "Cow's leash holder should be the player", expected {_player}, got leash holder of {_cow} - - # Test unleashing - unleash {_cow} - assert {_cow} is not leashed with "Cow should not be leashed after unleash effect" - assert leash holder of {_cow} is not set with "Cow's leash holder should not be set after unleashing" - - # Clean up - clear entity within {_cow} - clear entity within {_player} - -test "leashing boats": - # Test leashing a non-living entity (boat) - spawn a boat at test-location: - set {_boat} to entity - spawn a zombie at test-location: - set {_zombie} to entity - - # Leash the boat to the zombie - leash {_boat} to {_zombie} - assert {_boat} is leashed with "Boat should be leashed after leash effect" - assert leash holder of {_boat} is {_zombie} with "Boat's leash holder should be the zombie", expected {_zombie}, got leash holder of {_boat} - - # Test unleashing the boat - unleash {_boat} - assert {_boat} is not leashed with "Boat should not be leashed after unleash effect" - - # Clean up - clear entity within {_boat} - clear entity within {_zombie} - -test "leashing non-leashable entities": - # Test that non-leashable entities don't throw exceptions - spawn an armor stand at test-location: - set {_armorstand} to entity - spawn a pig at test-location: - set {_pig} to entity - - # Try to leash a non-leashable entity (should not throw exception) - leash {_armorstand} to {_pig} - assert {_armorstand} is not leashed with "Armor stand should not be leashed (not leashable)" - assert leash holder of {_armorstand} is not set with "Armor stand should have no leash holder" - - # Clean up - clear entity within {_armorstand} - clear entity within {_pig} - -test "leashing multiple entities": - # Test leashing multiple entities at once - spawn a sheep, a cow and a pig at test-location: - add entity to {_animals::*} spawn a villager at test-location: set {_holder} to entity - # Leash all animals to the villager - leash {_animals::*} to {_holder} - - loop {_animals::*}: - assert loop-value is leashed with "Animal %loop-value% should be leashed" - assert leash holder of loop-value is {_holder} with "Animal's leash holder should be the villager" - - # Unleash all animals - unleash {_animals::*} - - loop {_animals::*}: - assert loop-value is not leashed with "Animal %loop-value% should not be leashed after unleashing" - - # Clean up - clear entities within {_animals::*} - clear entity within {_holder} - -test "alternative leash syntax": - # Test the alternative "make X leash Y" syntax - spawn a wolf at test-location: - set {_wolf} to entity - spawn a player at test-location: - set {_player} to entity - - # Use alternative syntax - make {_player} leash {_wolf} - assert {_wolf} is leashed with "Wolf should be leashed using alternative syntax" - assert leash holder of {_wolf} is {_player} with "Wolf's leash holder should be the player" - - # Clean up - unleash holder of {_wolf} - clear entity within {_wolf} - clear entity within {_player} - -test "leash holder returns null for non-leashable": - # Test that getting leash holder of non-leashable entities returns null without exception - spawn an ender crystal at test-location: - set {_crystal} to entity - - set {_holder} to leash holder of {_crystal} - assert {_holder} is not set with "Leash holder of non-leashable entity should be null" - - # Clean up - clear entity within {_crystal} - -test "leash holder returns null for unleashed entity": - # Test that getting leash holder of unleashed leashable entity returns null - spawn a horse at test-location: - set {_horse} to entity - - set {_holder} to leash holder of {_horse} - assert {_holder} is not set with "Leash holder of unleashed entity should be null" - - # Clean up - clear entity within {_horse} + leash {_cow} to {_holder} + unleash {_cow} + delete entity within {_cow} + delete entity within {_holder}