diff --git a/src/main/java/ch/njol/skript/bukkitutil/UUIDUtils.java b/src/main/java/ch/njol/skript/bukkitutil/UUIDUtils.java new file mode 100644 index 00000000000..fef1709dfcd --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/UUIDUtils.java @@ -0,0 +1,63 @@ +package ch.njol.skript.bukkitutil; + +import ch.njol.util.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * Utility class for quick {@link UUID} methods. + */ +public class UUIDUtils { + + /** + * Converts {@code object} into a UUID if possible + * @param object + * @return The resulting {@link UUID} + */ + public static @Nullable UUID asUUID(@NotNull Object object) { + if (object instanceof OfflinePlayer offlinePlayer) { + return offlinePlayer.getUniqueId(); + } else if (object instanceof Entity entity) { + return entity.getUniqueId(); + } else if (object instanceof String string && StringUtils.containsAny(string, "-")) { + try { + return UUID.fromString(string); + } catch (Exception ignored) {} + } else if (object instanceof UUID uuid) { + return uuid; + } + return null; + } + + /** + * Get the {@link Entity} or {@link OfflinePlayer} the {@code uuid} belongs to. + * Will return an {@link OfflinePlayer} regardless if the player exists. + * @param uuid The {@link UUID} + * @return The resulting {@link Entity} or {@link OfflinePlayer} + */ + public static @Nullable Object fromUUID(@NotNull UUID uuid) { + return fromUUID(uuid, false); + } + + /** + * Get the {@link Entity} or {@link OfflinePlayer} the {@code uuid} belongs to. + * @param uuid The {@link UUID} + * @param restrictOffline If the {@link OfflinePlayer} needs to have played the server. + * @return The resulting {@link Entity} or {@link OfflinePlayer} + */ + public static @Nullable Object fromUUID(@NotNull UUID uuid, boolean restrictOffline) { + Entity checkEntity = Bukkit.getEntity(uuid); + if (checkEntity != null) + return checkEntity; + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + if (!restrictOffline || (restrictOffline && offlinePlayer.hasPlayedBefore())) + return offlinePlayer; + return null; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondItemDespawn.java b/src/main/java/ch/njol/skript/conditions/CondItemDespawn.java new file mode 100644 index 00000000000..062bb3584bd --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondItemDespawn.java @@ -0,0 +1,34 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.entity.Item; + +@Name("Will Despawn") +@Description("Checks if the dropped item will be despawned naturally through Minecraft's timer.") +@Examples({ + "if all dropped items can despawn naturally:", + "\tprevent all dropped items from naturally despawning" +}) +@Since("INSERT VERSION") +public class CondItemDespawn extends PropertyCondition { + + static { + PropertyCondition.register(CondItemDespawn.class, PropertyType.WILL, "(despawn naturally|naturally despawn)", "itementities"); + PropertyCondition.register(CondItemDespawn.class, PropertyType.CAN, "(despawn naturally|naturally despawn)", "itementities"); + } + + @Override + public boolean check(Item item) { + return !item.isUnlimitedLifetime(); + } + + @Override + protected String getPropertyName() { + return "naturally despawn"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffItemDespawn.java b/src/main/java/ch/njol/skript/effects/EffItemDespawn.java new file mode 100644 index 00000000000..16d2ddc3d7f --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffItemDespawn.java @@ -0,0 +1,62 @@ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Item; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Item Despawn") +@Description("Prevent a dropped item from naturally despawning through Minecraft's timer.") +@Examples({ + "prevent all dropped items from naturally despawning", + "allow all dropped items to naturally despawn" +}) +@Since("INSERT VERSION") +public class EffItemDespawn extends Effect { + + static { + Skript.registerEffect(EffItemDespawn.class, + "(prevent|disallow) %itementities% from (naturally despawning|despawning naturally)", + "allow natural despawning of %itementities%", + "allow %itementities% to (naturally despawn|despawn naturally)"); + } + + private Expression entities; + private boolean prevent; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + prevent = matchedPattern == 0; + //noinspection unchecked + entities = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Item item : entities.getArray(event)) { + item.setUnlimitedLifetime(prevent); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + if (prevent) { + builder.append("prevent", entities, "from naturally despawning"); + } else { + builder.append("allow", entities, "to naturally despawn"); + } + return builder.toString(); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityOwner.java b/src/main/java/ch/njol/skript/expressions/ExprEntityOwner.java new file mode 100644 index 00000000000..aaf38f32b0f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityOwner.java @@ -0,0 +1,107 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.UUIDUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Tameable; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@Name("Entity Owner") +@Description({ + "The owner of a tameable entity (i.e. horse or wolf) or a dropped item.", + "Getting the owner of a dropped item will only return a loaded entity or a player that has played before. " + + "If the entity was killed, or the player has never played before, will return null.", + "Setting the owner of a dropped item means only that entity or player can pick it up. " + + "This is UUID based, so it can also be set to a specific UUID.", + "Dropping an item does not automatically make the entity or player the owner." +}) +@Examples({ + "set owner of target entity to player", + "delete owner of target entity", + "set {_t} to uuid of tamer of target entity", + "", + "set the owner of all dropped items to player" +}) +@Since("2.5, INSERT VERSION (dropped items)") +public class ExprEntityOwner extends SimplePropertyExpression { + + static { + Skript.registerExpression(ExprEntityOwner.class, Object.class, ExpressionType.PROPERTY, + "[the] (owner|tamer) of %livingentities%", + "%livingentities%'[s] (owner|tamer)", + "[the] [dropped item] owner of %itementities%", + "%itementities%'[s] [dropped item] owner"); + } + + @Override + public @Nullable Object convert(Entity entity) { + if (entity instanceof Tameable tameable && tameable.isTamed()) { + return tameable.getOwner(); + } else if (entity instanceof Item item) { + UUID uuid = item.getOwner(); + if (uuid == null) + return null; + return UUIDUtils.fromUUID(uuid, true); + } + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(OfflinePlayer.class, Entity.class, String.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + UUID newId = null; + OfflinePlayer newPlayer = null; + if (delta != null) { + if (delta[0] instanceof OfflinePlayer offlinePlayer) { + newPlayer = offlinePlayer; + newId = offlinePlayer.getUniqueId(); + } else { + newId = UUIDUtils.asUUID(delta[0]); + } + } + + for (Entity entity : getExpr().getArray(event)) { + if (entity instanceof Tameable tameable) { + tameable.setOwner(newPlayer); + } else if (entity instanceof Item item) { + item.setOwner(newId); + } + } + } + + @Override + public Class getReturnType() { + return Object.class; + } + + @Override + public Class[] possibleReturnTypes() { + return CollectionUtils.array(Entity.class, OfflinePlayer.class); + } + + @Override + protected String getPropertyName() { + return "owner"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityTamer.java b/src/main/java/ch/njol/skript/expressions/ExprEntityTamer.java deleted file mode 100644 index 83f2aa43cc7..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityTamer.java +++ /dev/null @@ -1,85 +0,0 @@ -package ch.njol.skript.expressions; - -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Tameable; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.util.coll.CollectionUtils; - -@Name("Entity Owner") -@Description("The owner of a tameable entity, such as a horse or wolf.") -@Examples({"set owner of target entity to player", - "delete owner of target entity", - "set {_t} to uuid of tamer of target entity"}) -@Since("2.5") -public class ExprEntityTamer extends SimplePropertyExpression { - - static { - register(ExprEntityTamer.class, OfflinePlayer.class, "(owner|tamer)", "livingentities"); - } - - @Nullable - @Override - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.DELETE || mode == ChangeMode.RESET) - return CollectionUtils.array(OfflinePlayer.class); - return null; - } - - @Nullable - @Override - public OfflinePlayer convert(LivingEntity entity) { - if (entity instanceof Tameable) { - Tameable t = ((Tameable) entity); - if (t.isTamed()) { - return ((OfflinePlayer) t.getOwner()); - } - } - return null; - } - - @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - OfflinePlayer player = delta == null ? null : ((OfflinePlayer) delta[0]); - switch (mode) { - case SET: - for (LivingEntity entity : getExpr().getAll(e)) { - if (!(entity instanceof Tameable)) - continue; - ((Tameable) entity).setOwner(player); - } - break; - case DELETE: - case RESET: - for (LivingEntity entity : getExpr().getAll(e)) { - if (!(entity instanceof Tameable)) - continue; - ((Tameable) entity).setOwner(null); - } - } - } - - @Override - public Class getReturnType() { - return OfflinePlayer.class; - } - - @Override - protected String getPropertyName() { - return "entity owner"; - } - - @Override - public String toString(@Nullable Event e, boolean d) { - return "owner of " + getExpr().toString(e, d); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemThrower.java b/src/main/java/ch/njol/skript/expressions/ExprItemThrower.java new file mode 100644 index 00000000000..48f627395c1 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprItemThrower.java @@ -0,0 +1,79 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.bukkitutil.UUIDUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +@Name("Item Thrower") +@Description({ + "The entity that threw/dropped the dropped item.", + "Getting the item thrower will only return a living entity or a player that has played before. " + + "If the entity was killed, or the player has never played before, this will return null." +}) +@Examples({ + "broadcast the item thrower of all dropped items", + "set the dropped item thrower of {_dropped item} to player", + "clear the item thrower of {_dropped item}" +}) +@Since("INSERT VERSION") +public class ExprItemThrower extends SimplePropertyExpression { + + static { + registerDefault(ExprItemThrower.class, Object.class, "[dropped] item thrower", "itementities"); + } + + @Override + public @Nullable Object convert(Item item) { + UUID uuid = item.getThrower(); + if (uuid == null) + return null; + return UUIDUtils.fromUUID(uuid, true); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(OfflinePlayer.class, Entity.class, String.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + UUID newId = null; + if (delta != null) { + newId = UUIDUtils.asUUID(delta[0]); + } + + for (Item item : getExpr().getArray(event)) { + item.setThrower(newId); + } + } + + @Override + public Class getReturnType() { + return Object.class; + } + + @Override + public Class[] possibleReturnTypes() { + return CollectionUtils.array(Entity.class, OfflinePlayer.class); + } + + @Override + protected String getPropertyName() { + return "dropped item thrower"; + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffItemLifetime.sk b/src/test/skript/tests/syntaxes/effects/EffItemLifetime.sk new file mode 100644 index 00000000000..b6f48688d57 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffItemLifetime.sk @@ -0,0 +1,13 @@ +test "dropped item lifetime": + ### + Combines: + EffItemLifetime + CondItemLifetime + ### + drop a stone at test-location + set {_drop} to last dropped item + + prevent {_drop} from naturally despawning + assert {_drop} can not despawn naturally with "Dropped Item should not be able to despawn" + + clear entity within {_drop} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntityOwner.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntityOwner.sk new file mode 100644 index 00000000000..887b2512143 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprEntityOwner.sk @@ -0,0 +1,29 @@ +test "entity owner of tameable": + spawn a wolf at test-location: + assert event-entity is tameable with "entity is tameable condition not passing, last spawned wolf is not tameable" + set owner of event-entity to "Notch" parsed as offline player + assert "%owner of event-entity%" = "Notch" with "Owner of last spawned wolf was not set" + delete event-entity + +test "item owner of dropped item": + ### + Combines: + ExprItemOwner + ExprItemThrower + ### + drop a stone at test-location + set {_drop} to last dropped item + + spawn a sheep at test-location: + set {_owner} to entity + set the dropped item owner of {_drop} to {_owner} + assert the dropped item owner of {_drop} is {_owner} with "Item Owner was not set to sheep" + + spawn a pig at test-location: + set {_thrower} to entity + set the dropped item thrower of {_drop} to {_thrower} + assert the dropped item thrower of {_drop} is {_thrower} with "Item Thrower was not set to pig" + + clear entity within {_drop} + clear entity within {_owner} + clear entity within {_thrower} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk deleted file mode 100644 index 8d9aa911491..00000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk +++ /dev/null @@ -1,6 +0,0 @@ -test "entity tamer expression/condition": - spawn a wolf at test-location: - assert event-entity is tameable with "entity is tameable condition not passing, last spawned wolf is not tameable" - set owner of event-entity to "Notch" parsed as offline player - assert "%owner of event-entity%" = "Notch" with "Owner of last spawned wolf was not set" - delete event-entity