diff --git a/.github/workflows/java-21-builds.yml b/.github/workflows/java-21-builds.yml index b442fb29e..631b25d70 100644 --- a/.github/workflows/java-21-builds.yml +++ b/.github/workflows/java-21-builds.yml @@ -13,7 +13,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 1.21.3,1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 + environments: 1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 java_version: 21 parallel_jobs: 3 diff --git a/.github/workflows/java-25-builds.yml b/.github/workflows/java-25-builds.yml index ea1f23182..6313b9cc6 100644 --- a/.github/workflows/java-25-builds.yml +++ b/.github/workflows/java-25-builds.yml @@ -13,9 +13,9 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 26.1.2 + environments: 26.1.2,26.2 java_version: 25 - parallel_jobs: 3 + parallel_jobs: 1 build: name: ${{ matrix.display }} diff --git a/.github/workflows/junit-21-builds.yml b/.github/workflows/junit-21-builds.yml index e741c08dc..71dcde9b0 100644 --- a/.github/workflows/junit-21-builds.yml +++ b/.github/workflows/junit-21-builds.yml @@ -13,7 +13,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 1.21.3,1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 + environments: 1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 java_version: 21 parallel_jobs: 3 diff --git a/.github/workflows/junit-25-builds.yml b/.github/workflows/junit-25-builds.yml index 7633f6da9..1746b580e 100644 --- a/.github/workflows/junit-25-builds.yml +++ b/.github/workflows/junit-25-builds.yml @@ -13,9 +13,9 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 26.1.2 + environments: 26.1.2,26.2 java_version: 25 - parallel_jobs: 3 + parallel_jobs: 1 build: name: ${{ matrix.display }} diff --git a/build.gradle b/build.gradle index eeb403e66..846cad117 100644 --- a/build.gradle +++ b/build.gradle @@ -30,10 +30,9 @@ allprojects { } dependencies { - shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.2.1' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '26.1.2.build.+' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '26.2.build.+' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' // bundled with Minecraft 1.19.4+ for display entity transforms @@ -107,11 +106,9 @@ tasks.withType(ShadowJar).configureEach { project.configurations.shadow ] dependencies { - include(dependency('io.papermc:paperlib')) include(dependency('org.bstats:bstats-bukkit')) include(dependency('org.bstats:bstats-base')) } - relocate 'io.papermc.lib', 'ch.njol.skript.paperlib' relocate 'org.bstats', 'ch.njol.skript.bstats' manifest { attributes( diff --git a/gradle.properties b/gradle.properties index 1acbc62bb..b3a0225fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,9 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.15.4 +version=2.16.0 jarName=Skript.jar -testEnv=java25/paper-26.1.2 +testEnv=java25/paper-26.2 testEnvJavaVersion=25 folia_build=1 diff --git a/src/main/java/ch/njol/skript/aliases/AliasesParser.java b/src/main/java/ch/njol/skript/aliases/AliasesParser.java index 4f3531ac4..78300e1ef 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesParser.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesParser.java @@ -424,7 +424,7 @@ protected Map parseKeyVariations(String name) { List slots = new ArrayList<>(); // Compute variation slots - for (int i = 0; i < name.length();) { + for (int i = 0; i < name.length(); i += Character.charCount(name.codePointAt(i))) { int c = name.codePointAt(i); if (c == '{') { // Found variation name start varStart = i; @@ -444,16 +444,16 @@ protected Map parseKeyVariations(String name) { VariationGroup vars = provider.getVariationGroup(varName); if (vars == null) { Skript.error(m_unknown_variation.toString(varName)); + varStart = -1; + varEnd = i + 1; continue; } slots.add(new VariationSlot(vars)); - + // Variation name finished varStart = -1; varEnd = i + 1; } - - i += Character.charCount(c); } // Handle last non-variation slot @@ -690,6 +690,10 @@ protected void loadSingleAlias(Map variations, String item) { * @return Name fixed. */ protected String fixName(String name) { + if (name.isEmpty()) { + return ""; + } + /* * General logic: * diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index e4fe62aae..5b28f29e7 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -657,9 +657,6 @@ public ItemData aliasCopy() { return data; } - // Can remove after 1.21.3 is the minimum supported version - private static final boolean HAS_CUSTOM_NAME = Skript.methodExists(PotionMeta.class, "hasCustomPotionName"); - private static void copyPotionInfo(PotionMeta potionMeta, ItemData data) { PotionMeta newMeta = (PotionMeta) itemFactory.getItemMeta(data.type); if (newMeta.equals(potionMeta)) @@ -674,7 +671,7 @@ private static void copyPotionInfo(PotionMeta potionMeta, ItemData data) { } if (potionMeta.hasColor()) newMeta.setColor(potionMeta.getColor()); - if (HAS_CUSTOM_NAME && potionMeta.hasCustomPotionName()) + if (potionMeta.hasCustomPotionName()) newMeta.setCustomPotionName(potionMeta.getCustomPotionName()); data.itemFlags = ItemFlags.CHANGED_TAGS; data.setItemMeta(newMeta); diff --git a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java index 90795c36a..f6b26552b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java @@ -2,9 +2,6 @@ import ch.njol.skript.Skript; import ch.njol.util.Math2; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.attribute.Attributable; @@ -18,6 +15,9 @@ import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + public class HealthUtils { private static final @Nullable Constructor OLD_DAMAGE_EVENT_CONSTRUCTOR; @@ -31,11 +31,7 @@ public class HealthUtils { private static final Attribute MAX_HEALTH; static { - if (Skript.isRunningMinecraft(1, 21, 3)) { // In 1.21.3, Attribute became an Interface - MAX_HEALTH = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); - } else { - MAX_HEALTH = (Attribute) Enum.valueOf((Class) Attribute.class, "GENERIC_MAX_HEALTH"); - } + MAX_HEALTH = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); } /** diff --git a/src/main/java/ch/njol/skript/bukkitutil/SkriptTeleportFlag.java b/src/main/java/ch/njol/skript/bukkitutil/SkriptTeleportFlag.java deleted file mode 100644 index 28d1ba69c..000000000 --- a/src/main/java/ch/njol/skript/bukkitutil/SkriptTeleportFlag.java +++ /dev/null @@ -1,33 +0,0 @@ -package ch.njol.skript.bukkitutil; - -import io.papermc.paper.entity.TeleportFlag; -import io.papermc.paper.entity.TeleportFlag.EntityState; -import io.papermc.paper.entity.TeleportFlag.Relative; - -/** - * A utility enum for accessing Paper's teleport flags (1.19.4+) - */ -public enum SkriptTeleportFlag { - - RETAIN_OPEN_INVENTORY(EntityState.RETAIN_OPEN_INVENTORY), - RETAIN_PASSENGERS(EntityState.RETAIN_PASSENGERS), - RETAIN_VEHICLE(EntityState.RETAIN_VEHICLE), - RETAIN_DIRECTION(Relative.PITCH, Relative.YAW), - RETAIN_PITCH(Relative.PITCH), - RETAIN_YAW(Relative.YAW), - RETAIN_MOVEMENT(Relative.X, Relative.Y, Relative.Z), - RETAIN_X(Relative.X), - RETAIN_Y(Relative.Y), - RETAIN_Z(Relative.Z); - - final TeleportFlag[] teleportFlags; - - SkriptTeleportFlag(TeleportFlag... teleportFlags) { - this.teleportFlags = teleportFlags; - } - - public TeleportFlag[] getTeleportFlags() { - return teleportFlags; - } - -} diff --git a/src/main/java/ch/njol/skript/classes/EnumClassInfo.java b/src/main/java/ch/njol/skript/classes/EnumClassInfo.java index 86ed46c10..957173909 100644 --- a/src/main/java/ch/njol/skript/classes/EnumClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/EnumClassInfo.java @@ -3,8 +3,9 @@ import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.DefaultExpression; import ch.njol.util.coll.iterator.ArrayIterator; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.comparator.Relation; + +import java.util.Map; +import java.util.function.Consumer; /** * This class can be used for an easier writing of ClassInfos that are enums, @@ -20,17 +21,17 @@ public class EnumClassInfo> extends ClassInfo { * @param languageNode The language node of the type */ public EnumClassInfo(Class enumClass, String codeName, String languageNode) { - this(enumClass, codeName, languageNode, new EventValueExpression<>(enumClass), true); + this(enumClass, codeName, languageNode, new EventValueExpression<>(enumClass)); } /** * @param enumClass The class * @param codeName The name used in patterns * @param languageNode The language node of the type - * @param registerComparator Whether a default comparator should be registered for this enum's classinfo + * @param parseCallback A consumer to run on a successful parse. */ - public EnumClassInfo(Class enumClass, String codeName, String languageNode, boolean registerComparator) { - this(enumClass, codeName, languageNode, new EventValueExpression<>(enumClass), registerComparator); + public EnumClassInfo(Class enumClass, String codeName, String languageNode, Consumer parseCallback) { + this(enumClass, codeName, languageNode, new EventValueExpression<>(enumClass), parseCallback); } /** @@ -40,7 +41,7 @@ public EnumClassInfo(Class enumClass, String codeName, String languageNode, b * @param defaultExpression The default expression of the type */ public EnumClassInfo(Class enumClass, String codeName, String languageNode, DefaultExpression defaultExpression) { - this(enumClass, codeName, languageNode, defaultExpression, true); + this(enumClass, codeName, languageNode, defaultExpression, ignored -> {}); } /** @@ -48,19 +49,41 @@ public EnumClassInfo(Class enumClass, String codeName, String languageNode, D * @param codeName The name used in patterns * @param languageNode The language node of the type * @param defaultExpression The default expression of the type - * @param registerComparator Whether a default comparator should be registered for this enum's classinfo + * @param parseCallback A consumer to run on a successful parse. */ - public EnumClassInfo(Class enumClass, String codeName, String languageNode, DefaultExpression defaultExpression, boolean registerComparator) { + public EnumClassInfo(Class enumClass, String codeName, String languageNode, DefaultExpression defaultExpression, Consumer parseCallback) { super(enumClass, codeName); - EnumParser enumParser = new EnumParser<>(enumClass, languageNode); + EnumParser enumParser = new EnumParser<>(enumClass, languageNode, parseCallback); usage(enumParser.getCombinedPatterns()) .serializer(new EnumSerializer<>(enumClass)) .defaultExpression(defaultExpression) .supplier(() -> new ArrayIterator<>(enumClass.getEnumConstants())) .parser(enumParser); + } - if (registerComparator) - Comparators.registerComparator(enumClass, enumClass, (o1, o2) -> Relation.get(o1.ordinal() - o2.ordinal())); + /** + * @param enumClass The class + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param registerComparator Whether a default comparator should be registered for this enum's classinfo + * @deprecated {@code registerComparator} is no longer necessary. + */ + @Deprecated(since = "2.16", forRemoval = true) + public EnumClassInfo(Class enumClass, String codeName, String languageNode, boolean registerComparator) { + this(enumClass, codeName, languageNode, new EventValueExpression<>(enumClass)); + } + + /** + * @param enumClass The class + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param defaultExpression The default expression of the type + * @param registerComparator Whether a default comparator should be registered for this enum's classinfo + * @deprecated {@code registerComparator} is no longer necessary. + */ + @Deprecated(since = "2.16", forRemoval = true) + public EnumClassInfo(Class enumClass, String codeName, String languageNode, DefaultExpression defaultExpression, boolean registerComparator) { + this(enumClass, codeName, languageNode, defaultExpression); } } diff --git a/src/main/java/ch/njol/skript/classes/EnumParser.java b/src/main/java/ch/njol/skript/classes/EnumParser.java index 59cdf1a29..8d8ac955a 100644 --- a/src/main/java/ch/njol/skript/classes/EnumParser.java +++ b/src/main/java/ch/njol/skript/classes/EnumParser.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.function.Consumer; /** * A {@link Parser} used for parsing and handling values representing an {@link Enum} @@ -19,6 +20,8 @@ public class EnumParser> extends PatternedParser implements private final Class enumClass; private final String languageNode; + private final Consumer parseCallback; + private String[] names; protected final Map parseMap = new HashMap<>(); private String[] patterns; @@ -28,11 +31,21 @@ public class EnumParser> extends PatternedParser implements * @param languageNode The {@link String} representing the languageNode for the {@link Enum} */ public EnumParser(Class enumClass, String languageNode) { + this(enumClass, languageNode, ignored -> {}); + } + + /** + * @param enumClass The {@link Enum} {@link Class} to be accessed. + * @param languageNode The {@link String} representing the languageNode for the {@link Enum} + * @param parseCallback A consumer to run on a successful parse. + */ + public EnumParser(Class enumClass, String languageNode, Consumer parseCallback) { assert enumClass.isEnum() : enumClass; assert !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; this.enumClass = enumClass; this.languageNode = languageNode; + this.parseCallback = parseCallback; refresh(); Language.addListener(this::refresh); @@ -86,7 +99,11 @@ void refresh() { @Override public @Nullable E parse(String string, ParseContext context) { - return parseMap.get(string.toLowerCase(Locale.ENGLISH)); + E element = parseMap.get(string.toLowerCase(Locale.ENGLISH)); + if (element != null) { + parseCallback.accept(element); + } + return element; } @Override diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 0e7cb7627..3ad45cd73 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1,9 +1,7 @@ package ch.njol.skript.classes.data; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.bukkitutil.SkriptTeleportFlag; import ch.njol.skript.classes.*; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.expressions.ExprDamageCause; @@ -24,8 +22,6 @@ import org.bukkit.block.banner.PatternType; import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; @@ -424,17 +420,6 @@ public boolean mustSyncDeserialization() { } })); - Classes.registerClass(new RegistryClassInfo<>(Enchantment.class, Registry.ENCHANTMENT, "enchantment", "enchantments") - .user("enchantments?") - .name("Enchantment") - .description("An enchantment, e.g. 'sharpness' or 'fortune'. Unlike enchantment type " + - "this type has no level, but you usually don't need to use this type anyway.", - "NOTE: Minecraft namespaces are supported, ex: 'minecraft:basalt_deltas'.", - "As of Minecraft 1.21 this will also support custom enchantments using namespaces, ex: 'myenchants:explosive'.") - .examples("") - .since("1.4.6") - .before("enchantmenttype")); - Material[] allMaterials = Material.values(); Classes.registerClass(new ClassInfo<>(Material.class, "material") .name(ClassInfo.NO_DOC) @@ -615,31 +600,6 @@ public String[] getPatterns() { ExpressionPropertyHandler.of(GameRule::getName, String.class) )); - Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") - .user("enchant[ment][ ]offers?") - .name("Enchantment Offer") - .description("The enchantmentoffer in an enchant prepare event.") - .examples("on enchant prepare:", - "\tset enchant offer 1 to sharpness 1", - "\tset the cost of enchant offer 1 to 10 levels") - .since("2.5") - .parser(new Parser<>() { - @Override - public boolean canParse(ParseContext context) { - return false; - } - - @Override - public String toString(EnchantmentOffer eo, int flags) { - return Classes.toString(eo.getEnchantment()) + " " + eo.getEnchantmentLevel(); - } - - @Override - public String toVariableNameString(EnchantmentOffer eo) { - return "offer:" + Classes.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); - } - })); - Classes.registerClass(new RegistryClassInfo<>(Attribute.class, Registry.ATTRIBUTE, "attributetype", "attribute types") .user("attribute ?types?") .name("Attribute Type") @@ -739,31 +699,6 @@ public String toVariableNameString(EntitySnapshot snapshot) { })); } - Classes.registerClass(new ClassInfo<>(WorldBorder.class, "worldborder") - .user("world ?borders?") - .name("World Border") - .description("Represents the border of a world or player.") - .since("2.11") - .parser(new Parser<>() { - @Override - public boolean canParse(ParseContext context) { - return false; - } - - @Override - public String toString(WorldBorder border, int flags) { - if (border.getWorld() == null) - return "virtual world border"; - return "world border of world named '" + border.getWorld().getName() + "'"; - } - - @Override - public String toVariableNameString(WorldBorder border) { - return toString(border, 0); - } - }) - .defaultExpression(new EventValueExpression<>(WorldBorder.class))); - Classes.registerClass(new ClassInfo<>(org.bukkit.block.banner.Pattern.class, "bannerpattern") .user("banner ?patterns?") .name("Banner Pattern") @@ -796,13 +731,6 @@ public String toVariableNameString(WorldBorder border) { .description("Represents the various banner patterns that can be applied to a banner.") .since("2.10")); - if (Skript.classExists("io.papermc.paper.entity.TeleportFlag")) - Classes.registerClass(new EnumClassInfo<>(SkriptTeleportFlag.class, "teleportflag", "teleport flags") - .user("teleport ?flags?") - .name("Teleport Flag") - .description("Teleport Flags are settings to retain during a teleport.") - .since("2.10")); - Classes.registerClass(new ClassInfo<>(Vehicle.class, "vehicle") .user("vehicles?") .name("Vehicle") diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index d04dd7b28..8f6156506 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -62,6 +62,7 @@ import org.bukkit.inventory.*; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; +import org.skriptlang.skript.bukkit.item.book.elements.expressions.ExprBookPages; import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue.Time; import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; @@ -576,11 +577,11 @@ else if (hand == EquipmentSlot.OFF_HAND) return book; })); registry.register(EventValue.builder(PlayerEditBookEvent.class, Component[].class) - .getter(event -> event.getPreviousBookMeta().pages().toArray(new Component[0])) + .getter(event -> ExprBookPages.getPages(event.getPreviousBookMeta()).toArray(new Component[0])) .time(Time.PAST) .build()); registry.register(EventValue.simple(PlayerEditBookEvent.class, Component[].class, event -> - event.getNewBookMeta().pages().toArray(new Component[0]))); + ExprBookPages.getPages(event.getNewBookMeta()).toArray(new Component[0]))); //ItemDespawnEvent registry.register(EventValue.simple(ItemDespawnEvent.class, Item.class, ItemDespawnEvent::getEntity)); registry.register(EventValue.simple(ItemDespawnEvent.class, ItemStack.class, event -> event.getEntity().getItemStack())); @@ -807,35 +808,6 @@ else if (hand == EquipmentSlot.OFF_HAND) registry.register(EventValue.simple(PlayerElytraBoostEvent.class, Entity.class, PlayerElytraBoostEvent::getFirework)); } - // === WorldBorderEvents === - if (Skript.classExists("io.papermc.paper.event.world.border.WorldBorderEvent")) { - // WorldBorderEvent - registry.register(EventValue.simple(WorldBorderEvent.class, WorldBorder.class, WorldBorderEvent::getWorldBorder)); - - // WorldBorderBoundsChangeEvent - registry.register(EventValue.simple(WorldBorderBoundsChangeEvent.class, Number.class, WorldBorderBoundsChangeEvent::getNewSize)); - registry.register(EventValue.builder(WorldBorderBoundsChangeEvent.class, Number.class) - .getter(WorldBorderBoundsChangeEvent::getOldSize) - .time(Time.PAST) - .build()); - registry.register(EventValue.simple(WorldBorderBoundsChangeEvent.class, Timespan.class, event -> new Timespan(event.getDuration()))); - - // WorldBorderBoundsChangeFinishEvent - registry.register(EventValue.simple(WorldBorderBoundsChangeFinishEvent.class, Number.class, WorldBorderBoundsChangeFinishEvent::getNewSize)); - registry.register(EventValue.builder(WorldBorderBoundsChangeFinishEvent.class, Number.class) - .getter(WorldBorderBoundsChangeFinishEvent::getOldSize) - .time(Time.PAST) - .build()); - registry.register(EventValue.simple(WorldBorderBoundsChangeFinishEvent.class, Timespan.class, event -> new Timespan((long) event.getDuration()))); - - // WorldBorderCenterChangeEvent - registry.register(EventValue.simple(WorldBorderCenterChangeEvent.class, Location.class, WorldBorderCenterChangeEvent::getNewCenter)); - registry.register(EventValue.builder(WorldBorderCenterChangeEvent.class, Location.class) - .getter(WorldBorderCenterChangeEvent::getOldCenter) - .time(Time.PAST) - .build()); - } - if (Skript.classExists("org.bukkit.event.block.VaultDisplayItemEvent")) { registry.register(EventValue.builder(VaultDisplayItemEvent.class, ItemStack.class) .getter(VaultDisplayItemEvent::getDisplayItem) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index fee9c1ebc..9dee9a86b 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -445,19 +445,6 @@ public boolean supportsOrdering() { } }); - // StructureType - StructureType - Comparators.registerComparator(StructureType.class, StructureType.class, new Comparator() { - @Override - public Relation compare(StructureType s1, StructureType s2) { - return Relation.get(CollectionUtils.containsAll(s2.getTypes(), s2.getTypes())); - } - - @Override - public boolean supportsOrdering() { - return false; - } - }); - // Object - ClassInfo Comparators.registerComparator(Object.class, ClassInfo.class, new Comparator() { @Override @@ -558,35 +545,6 @@ public boolean supportsOrdering() { } }); - // EnchantmentOffer Comparators - // EnchantmentOffer - EnchantmentType - Comparators.registerComparator(EnchantmentOffer.class, EnchantmentType.class, new Comparator() { - @Override - public Relation compare(EnchantmentOffer eo, EnchantmentType et) { - return Relation.get(eo.getEnchantment() == et.getType() && eo.getEnchantmentLevel() == et.getLevel()); - } - - @Override - public boolean supportsOrdering() { - return false; - } - }); - // EnchantmentOffer - Experience - Comparators.registerComparator(EnchantmentOffer.class, Experience.class, new Comparator() { - @Override - public Relation compare(EnchantmentOffer eo, Experience exp) { - return Relation.get(eo.getCost() == exp.getXP()); - } - - @Override public boolean supportsOrdering() { - return false; - } - }); - - //EnchantmentType - Enchantment - Comparators.registerComparator(EnchantmentType.class, Enchantment.class, ((enchantmentType, enchantment) -> - Relation.get(enchantmentType.getType().equals(enchantment)))); - Comparators.registerComparator(Inventory.class, InventoryType.class, new Comparator() { @Override public Relation compare(Inventory inventory, InventoryType inventoryType) { diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index d27fbf7fd..f87f7eb49 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -21,8 +21,6 @@ import org.bukkit.block.DoubleChest; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.entity.Entity; import org.bukkit.entity.EntitySnapshot; import org.bukkit.entity.LivingEntity; @@ -114,12 +112,6 @@ public DefaultConverters() {} // Block - ItemType Converters.registerConverter(Block.class, ItemType.class, ItemType::new, Converter.NO_LEFT_CHAINING | Commands.CONVERTER_NO_COMMAND_ARGUMENTS); - // Block - Location - Converters.registerConverter(Block.class, Location.class, BlockUtils::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); - - // Entity - Location - Converters.registerConverter(Entity.class, Location.class, Entity::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); - // Entity - EntityData Converters.registerConverter(Entity.class, EntityData.class, EntityData::fromEntity, Commands.CONVERTER_NO_COMMAND_ARGUMENTS | Converter.NO_RIGHT_CHAINING); @@ -285,34 +277,9 @@ public void setAmount(Number amount) { Converter.NO_RIGHT_CHAINING); } - // InventoryHolder - Location - // since the individual ones can't be trusted to chain. - Converters.registerConverter(InventoryHolder.class, Location.class, holder -> { - if (holder instanceof Entity entity) - return entity.getLocation(); - if (holder instanceof Block block) - return block.getLocation(); - if (holder instanceof BlockState state) - return BlockUtils.getLocation(state.getBlock()); - if (holder instanceof DoubleChest doubleChest) { - if (doubleChest.getLeftSide() != null) { - return BlockUtils.getLocation(((BlockState) doubleChest.getLeftSide()).getBlock()); - } else if (doubleChest.getRightSide() != null) { - return BlockUtils.getLocation(((BlockState) doubleChest.getRightSide()).getBlock()); - } - } - return null; - }); - - // Enchantment - EnchantmentType - Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); - // Vector - Direction Converters.registerConverter(Vector.class, Direction.class, Direction::new); - // EnchantmentOffer - EnchantmentType - Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, eo -> new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel())); - Converters.registerConverter(String.class, World.class, Bukkit::getWorld); if (Skript.classExists("org.bukkit.entity.EntitySnapshot")) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index ff604c3e9..818b402af 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -18,6 +18,7 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; @@ -696,6 +697,24 @@ public OfflinePlayer[] executeSimple(Object[][] params) { .since("2.8.0, 2.9.0 (prevent lookups)"); } // end offline player function + Functions.register(DefaultFunction.builder(skript, "entity", Entity.class) + .description( + "Returns an entity from a given UUID.", + "If the entity is unloaded or an offline player, the function will return nothing." + ) + .examples( + "set {_entity} to entity({uuid})", + "kill entity({uuid})", + "set {_type} to type of entity({uuid})" + ) + .since("2.16") + .parameter("uuid", UUID.class) + .build(args -> { + UUID uuid = args.get("uuid"); + return Bukkit.getEntity(uuid); + }) + ); + Functions.registerFunction(new SimpleJavaFunction("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { @Override public Boolean[] executeSimple(Object[][] params) { diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 64d878769..a5e5f69c2 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -374,59 +374,13 @@ public String toVariableNameString(Color color) { } })); - Classes.registerClass(new ClassInfo<>(StructureType.class, "structuretype") + Classes.registerClass(new EnumClassInfo<>(TreeSpecies.class, "treetype", "tree types") .user("tree ?types?", "trees?") .name("Tree Type") .description("A tree type represents a tree species or a huge mushroom species. These can be generated in a world with the generate tree effect.") - .usage("[any] , e.g. tree/any jungle tree/etc.", ", e.g. red mushroom/small jungle tree/big regular tree/etc.") .examples("grow any regular tree at the block", "grow a huge red mushroom above the block") - .since("") - .defaultExpression(new SimpleLiteral<>(StructureType.TREE, true)) - .parser(new Parser() { - @Override - @Nullable - public StructureType parse(final String s, final ParseContext context) { - return StructureType.fromName(s); - } - - @Override - public String toString(final StructureType o, final int flags) { - return o.toString(flags); - } - - @Override - public String toVariableNameString(final StructureType o) { - return "" + o.name().toLowerCase(Locale.ENGLISH); - } - }).serializer(new EnumSerializer<>(StructureType.class))); - - Classes.registerClass(new ClassInfo<>(EnchantmentType.class, "enchantmenttype") - .user("enchant(ing|ment) types?") - .name("Enchantment Type") - .description("An enchantment with an optional level, e.g. 'sharpness 2' or 'fortune'.") - .usage(" []") - .examples("enchant the player's tool with sharpness 5", - "helmet is enchanted with waterbreathing") - .since("1.4.6") - .parser(new Parser() { - @Override - @Nullable - public EnchantmentType parse(final String s, final ParseContext context) { - return EnchantmentType.parse(s); - } - - @Override - public String toString(final EnchantmentType t, final int flags) { - return t.toString(); - } - - @Override - public String toVariableNameString(final EnchantmentType o) { - return o.toString(); - } - }) - .serializer(new YggdrasilSerializer<>())); + .since("1.0")); Classes.registerClass(new ClassInfo<>(Experience.class, "experience") .user("experience ?(points?)?") diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java index 012b59d7d..ee197f036 100644 --- a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java @@ -5,8 +5,8 @@ import ch.njol.skript.lang.DefaultExpression; import org.bukkit.Keyed; import org.bukkit.Registry; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.comparator.Relation; + +import java.util.function.Consumer; /** * This class can be used for easily creating ClassInfos for {@link Registry}s. @@ -23,7 +23,7 @@ public class RegistryClassInfo extends ClassInfo { * @param languageNode The language node of the type */ public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode) { - this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass), true); + this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass)); } /** @@ -31,10 +31,10 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param registry The registry * @param codeName The name used in patterns * @param languageNode The language node of the type - * @param registerComparator Whether a default comparator should be registered for this registry's classinfo + * @param parseCallback A consumer to run on a successful parse. */ - public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, boolean registerComparator) { - this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass), registerComparator); + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, Consumer parseCallback) { + this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass), parseCallback); } /** @@ -45,7 +45,7 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param defaultExpression The default expression of the type */ public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression) { - this(registryClass, registry, codeName, languageNode, defaultExpression, true); + this(registryClass, registry, codeName, languageNode, defaultExpression, ignored -> {}); } /** @@ -54,19 +54,43 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param codeName The name used in patterns * @param languageNode The language node of the type * @param defaultExpression The default expression of the type - * @param registerComparator Whether a default comparator should be registered for this registry's classinfo + * @param parseCallback A consumer to run on a successful parse. */ - public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression, boolean registerComparator) { + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression, Consumer parseCallback) { super(registryClass, codeName); - RegistryParser registryParser = new RegistryParser<>(registry, languageNode); + RegistryParser registryParser = new RegistryParser<>(registry, languageNode, parseCallback); usage(registryParser.getCombinedPatterns()) .supplier(registry::iterator) - .serializer(new RegistrySerializer(registry)) + .serializer(new RegistrySerializer<>(registry)) .defaultExpression(defaultExpression) .parser(registryParser); + } - if (registerComparator) - Comparators.registerComparator(registryClass, registryClass, (o1, o2) -> Relation.get(o1.getKey().equals(o2.getKey()))); + /** + * @param registryClass The registry class + * @param registry The registry + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param registerComparator Whether a default comparator should be registered for this registry's classinfo + * @deprecated {@code registerComparator} is no longer necessary. + */ + @Deprecated(since = "2.16", forRemoval = true) + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, boolean registerComparator) { + this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass)); + } + + /** + * @param registryClass The registry class + * @param registry The registry + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param defaultExpression The default expression of the type + * @param registerComparator Whether a default comparator should be registered for this registry's classinfo + * @deprecated {@code registerComparator} is no longer necessary. + */ + @Deprecated(since = "2.16", forRemoval = true) + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression, boolean registerComparator) { + this(registryClass, registry, codeName, languageNode, defaultExpression); } } diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java index a86856f22..f2d0cba2c 100644 --- a/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.function.Consumer; /** * A parser based on a {@link Registry} used to parse data from a string or turn data into a string. @@ -24,15 +25,21 @@ public class RegistryParser extends PatternedParser { private final Registry registry; private final String languageNode; + private final Consumer parseCallback; private final Map names = new HashMap<>(); private final Map parseMap = new HashMap<>(); private String[] patterns; public RegistryParser(Registry registry, String languageNode) { + this(registry, languageNode, ignored -> {}); + } + + public RegistryParser(Registry registry, String languageNode, Consumer parseCallback) { assert !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; this.registry = registry; this.languageNode = languageNode; + this.parseCallback = parseCallback; refresh(); Language.addListener(this::refresh); } @@ -102,7 +109,11 @@ private void refresh() { */ @Override public @Nullable R parse(String input, @NotNull ParseContext context) { - return parseMap.get(input.toLowerCase(Locale.ENGLISH)); + R element = parseMap.get(input.toLowerCase(Locale.ENGLISH)); + if (element != null) { + parseCallback.accept(element); + } + return element; } /** diff --git a/src/main/java/ch/njol/skript/conditions/CondLeashed.java b/src/main/java/ch/njol/skript/conditions/CondLeashed.java index dcc4be333..17228bdfe 100644 --- a/src/main/java/ch/njol/skript/conditions/CondLeashed.java +++ b/src/main/java/ch/njol/skript/conditions/CondLeashed.java @@ -5,21 +5,22 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; +import io.papermc.paper.entity.Leashable; @Name("Is Leashed") @Description("Checks to see if an entity is currently leashed.") @Example("target entity is leashed") -@Since("2.5") -public class CondLeashed extends PropertyCondition { +@Since("2.5, 2.16 (all leashable entities)") +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) { + return entity instanceof Leashable leashable && leashable.isLeashed(); } @Override diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java index 5c9d430be..adf046694 100644 --- a/src/main/java/ch/njol/skript/doc/JSONGenerator.java +++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java @@ -254,7 +254,20 @@ private JsonObject generatedAnnotatedElement(SyntaxInfo syntaxInfo) { String name = Objects.requireNonNullElse(exact.getDocName(), exact.getName().getSingular()); if (name.equals(ClassInfo.NO_DOC)) { // undocumented type is not helpful // hopefully the supertype has something better... - exact = Classes.getSuperClassInfo(expression.returnType().getSuperclass()); + Class parent = expression.returnType().getSuperclass(); + if (parent == null || parent == Object.class) { + for (Class parentInterface : expression.returnType().getInterfaces()) { + exact = Classes.getSuperClassInfo(parentInterface); + if (exact.getC() != Object.class) { + parent = parentInterface; + break; + } + } + if (parent == null) { + parent = Object.class; + } + } + exact = Classes.getSuperClassInfo(parent); name = Objects.requireNonNullElse(exact.getDocName(), exact.getName().getSingular()); } diff --git a/src/main/java/ch/njol/skript/effects/Delay.java b/src/main/java/ch/njol/skript/effects/Delay.java index f19318b53..ed5647500 100644 --- a/src/main/java/ch/njol/skript/effects/Delay.java +++ b/src/main/java/ch/njol/skript/effects/Delay.java @@ -1,17 +1,18 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.EffectSection; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.lang.util.SectionUtils; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.region.TaskUtils; import ch.njol.skript.util.region.scheduler.Scheduler; @@ -25,30 +26,50 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.WeakHashMap; @Name("Delay") -@Description("Delays the script's execution by a given timespan. Please note that delays are not persistent, e.g. trying to create a tempban script with ban player → wait 7 days → unban player will not work if you restart your server anytime within these 7 days. You also have to be careful even when using small delays!") +@Description({ + "Delays the script's execution by a given timespan.", + "When used as an effect, all code after the wait runs once the delay elapses. The whole trigger pauses until the wait finishes.", + "When used as a section, only the code within the section is deferred. Code after the section continues immediately, and the body runs on the same event once the delay elapses. This is useful for scheduling follow-up work without blocking the rest of the trigger.", + "Note that delays are not persistent. For example, ban player → wait 7 days → unban player will not resume if the server restarts during the delay.", + "Inside a section body, outer loop-values are not available because the body is parsed as a separate trigger. Event values (like player) still work. If you need a value from before the delay, copy it to a local variable. Local variables are snapshotted when the section is scheduled, so subsequent changes aren't reflected." +}) @Example("wait 2 minutes") @Example("halt for 5 minecraft hours") @Example("wait a tick") -@Since("1.4") -public class Delay extends Effect { +@Example(""" + # Section form: outer continues immediately, body runs 3 seconds later. + send "hello" to player + wait 3 seconds: + send "...and goodbye" to player + """) +@Example(""" + send "This runs first!" to player + wait 1 seconds: + send "This runs third! (one second later)" to player + send "This runs second!" to player + """) +@Since("1.4, 2.16 (Delayed Sections)") +public class Delay extends EffectSection { static { - Skript.registerEffect(Delay.class, "(wait|halt) [for] %timespan% [for %-entity/location/block%]"); + Skript.registerSection(Delay.class, "(wait|halt) [for] %timespan% [for %-entity/location/block%]"); } @SuppressWarnings("NotNullFieldNotInitialized") protected Expression duration; private Expression object; + private @Nullable Trigger trigger; + @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - getParser().setHasDelayBefore(Kleenean.TRUE); - + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, + @Nullable SectionNode sectionNode, @Nullable List triggerItems) { duration = (Expression) exprs[0]; if (duration instanceof Literal) { // If we can, do sanity check for delays Timespan timespan = ((Literal) duration).getSingle(); @@ -61,69 +82,82 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Skript.warning("Delays less than one tick are not possible, defaulting to one tick."); } } + + if (hasSection()) { + assert sectionNode != null; + // Parse the body under the outer event context so event values still resolve inside it. + // Type hints are propagated via SectionUtils; locals are isolated at runtime via the swap dance in walk(). + Class[] outerEvents = getParser().getCurrentEvents(); + trigger = SectionUtils.loadDelayableLinkedCode("wait", (beforeLoading, afterLoading) -> { + Runnable bodyBefore = () -> { + beforeLoading.run(); + getParser().setHasDelayBefore(Kleenean.TRUE); + }; + return loadCode(sectionNode, "wait", bodyBefore, afterLoading, outerEvents); + }); + // Outer trigger is NOT delayed - code after the section runs immediately. + return trigger != null; + } + this.object = (Expression) exprs[1]; + getParser().setHasDelayBefore(Kleenean.TRUE); return true; } @Override - @Nullable - protected TriggerItem walk(Event event) { + protected @Nullable TriggerItem walk(Event event) { debug(event, true); long start = Skript.debug() ? System.nanoTime() : 0; - TriggerItem next = getNext(); - if (next != null && Skript.getInstance().isEnabled()) { // See https://github.com/SkriptLang/Skript/issues/3702 - Timespan duration = this.duration.getSingle(event); - if (duration == null) - return null; + if (!Skript.getInstance().isEnabled()) { // See https://github.com/SkriptLang/Skript/issues/3702 + error("Cannot delay code execution while the server is shutting down. The delay will be ignored."); + return trigger != null ? super.walk(event, false) : null; + } - Scheduler scheduler; - Object object = null; - if (this.object != null) { - object = this.object.getOptionalSingle(event).orElse(null); - } + Timespan duration = this.duration.getSingle(event); + if (duration == null) + return trigger != null ? super.walk(event, false) : null; - if (object instanceof Entity entity) { - scheduler = TaskUtils.getEntityScheduler(entity); - } else if (object instanceof Location location) { - scheduler = TaskUtils.getRegionalScheduler(location); - } else if (object instanceof Block block) { - scheduler = TaskUtils.getRegionalScheduler(block.getLocation()); - } else { - scheduler = TaskUtils.getGlobalScheduler(); - } + long ticks = Math.max(duration.getAs(Timespan.TimePeriod.TICK), 1); // Minimum delay is one tick, less than it is useless! - // Back up local variables - Object localVars = Variables.removeLocals(event); + Scheduler scheduler; + Object object = null; + if (this.object != null) { + object = this.object.getOptionalSingle(event).orElse(null); + } + + if (object instanceof Entity entity) { + scheduler = TaskUtils.getEntityScheduler(entity); + } else if (object instanceof Location location) { + scheduler = TaskUtils.getRegionalScheduler(location); + } else if (object instanceof Block block) { + scheduler = TaskUtils.getRegionalScheduler(block.getLocation()); + } else { + scheduler = TaskUtils.getGlobalScheduler(); + } + + TriggerItem afterDelay = trigger != null ? trigger : getNext(); + if (afterDelay != null) { + boolean isSection = trigger != null; + Object localVars = isSection ? Variables.copyLocalVariables(event) : Variables.removeLocals(event); scheduler.runTaskLater(() -> { - addDelayedEvent(event); + addDelayedEvent(event); Skript.debug(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1_000_000_000. + "s"); - // Re-set local variables if (localVars != null) Variables.setLocalVariables(event, localVars); - Object timing = null; // Timings reference must be kept so that it can be stopped after TriggerItem execution - if (SkriptTimings.enabled()) { // getTrigger call is not free, do it only if we must - Trigger trigger = getTrigger(); - if (trigger != null) - timing = SkriptTimings.start(trigger.getDebugLabel()); - } + TriggerItem.walk(afterDelay, event); - TriggerItem.walk(next, event); Variables.removeLocals(event); // Clean up local vars, we may be exiting now - - SkriptTimings.stop(timing); // Stop timing if it was even started - }, Math.max(duration.getAs(Timespan.TimePeriod.TICK), 1)); // Minimum delay is one tick, less than it is useless! + }, ticks); } - return null; - } - @Override - protected void execute(Event event) { - throw new UnsupportedOperationException(); + if (trigger != null) + return super.walk(event, false); + return null; } @Override diff --git a/src/main/java/ch/njol/skript/effects/EffInsertEntityStorage.java b/src/main/java/ch/njol/skript/effects/EffInsertEntityStorage.java index cdac3816e..796cbb585 100644 --- a/src/main/java/ch/njol/skript/effects/EffInsertEntityStorage.java +++ b/src/main/java/ch/njol/skript/effects/EffInsertEntityStorage.java @@ -39,11 +39,9 @@ public class EffInsertEntityStorage extends Effect { private static final Map, Class> STORAGES = new HashMap<>(); static { - if (Skript.isRunningMinecraft(1, 21, 0)) { - Skript.registerEffect(EffInsertEntityStorage.class, - "(add|insert) %livingentities% [in[ ]]to [the] (stored entities|entity storage) of %block%"); - STORAGES.put(Beehive.class, Bee.class); - } + Skript.registerEffect(EffInsertEntityStorage.class, + "(add|insert) %livingentities% [in[ ]]to [the] (stored entities|entity storage) of %block%"); + STORAGES.put(Beehive.class, Bee.class); } private Expression entities; diff --git a/src/main/java/ch/njol/skript/effects/EffLeash.java b/src/main/java/ch/njol/skript/effects/EffLeash.java index 0d3cccbc1..0f4717f9c 100644 --- a/src/main/java/ch/njol/skript/effects/EffLeash.java +++ b/src/main/java/ch/njol/skript/effects/EffLeash.java @@ -1,7 +1,6 @@ package ch.njol.skript.effects; import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -14,31 +13,30 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import io.papermc.paper.entity.Leashable; @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, or unleash them.", + "Most mobs, iron golems, snow golems, and boats can be leashed." }) @Example(""" on right click: leash event-entity to player send "&aYou leashed &2%event-entity%!" to player """) -@Since("2.3") +@Since("2.3, 2.16 (all leashable entities)") 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,24 +45,23 @@ 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; } @Override - protected void execute(Event e) { - if (leash) { - Entity holder = this.holder.getSingle(e); - if (holder == null) - return; - for (LivingEntity target : targets.getArray(e)) - target.setLeashHolder(holder); - } else { - for (LivingEntity target : targets.getArray(e)) - target.setLeashHolder(null); + protected void execute(Event event) { + Entity holder = leash ? this.holder.getSingle(event) : null; + if (leash && holder == null) + return; + + for (Entity target : targets.getArray(event)) { + if (target instanceof Leashable leashable) { + leashable.setLeashHolder(holder); + } } } diff --git a/src/main/java/ch/njol/skript/effects/EffOpenInventory.java b/src/main/java/ch/njol/skript/effects/EffOpenInventory.java index 942ead051..57208b361 100644 --- a/src/main/java/ch/njol/skript/effects/EffOpenInventory.java +++ b/src/main/java/ch/njol/skript/effects/EffOpenInventory.java @@ -11,7 +11,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Patterns; -import ch.njol.skript.util.Version; import ch.njol.util.Kleenean; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -44,13 +43,6 @@ public class EffOpenInventory extends Effect { Skript.registerEffect(EffOpenInventory.class, PATTERNS.getPatterns()); } - // Even though the actual api was added in 1.21.1, back then there wasn't support for creating inventory views with a null title. - // Fallback to the older Bukkit api is thus done to avoid errors. - // See bugged (1.21.1 - 1.21.3): https://github.com/PaperMC/Paper/blob/fbea3cdc0caca69814e5ab68b981fa0bdbe5331d/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java - // See Fixed (1.21.4+): https://github.com/PaperMC/Paper/blob/8eb8e44ac32a99f53da7af50e800ac8831030580/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMenuType.java - private static final boolean SUPPORT_MENU_TYPE = Skript.classExists("org.bukkit.inventory.MenuType") - && Skript.getMinecraftVersion().isLargerThan(new Version(1, 21, 3)); - private boolean open; private @Nullable InventoryType inventoryType = null; private @Nullable Expression inventoryExpr = null; @@ -114,12 +106,9 @@ private void openForPlayers(Event event, Object target) { @SuppressWarnings("UnstableApiUsage") private void openInventoryType(Player player, InventoryType type) { - - if (SUPPORT_MENU_TYPE) { - if (type.getMenuType() != null) { - player.openInventory(type.getMenuType().create(player, null)); - return; - } + if (type.getMenuType() != null) { + player.openInventory(type.getMenuType().create(player, null)); + return; } player.openInventory(Bukkit.createInventory(null, type)); diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index 293326a8e..f3446bd4a 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -40,7 +40,7 @@ # Very simple chat censor on chat: replace all "idiot" and "noob" with "****" in the message - regex replace "\b(idiot|noob)\b" with "****" in the message # Regex version using word boundaries for better results + regex replace "\\b(idiot|noob)\\b" with "****" in the message # Regex version using word boundaries for better results """) @Example("replace all stone and dirt in player's inventory and player's top inventory with diamond") @Since("2.0, 2.2-dev24 (multiple strings, items in inventory), 2.5 (replace first, case sensitivity), 2.10 (regex)") @@ -144,11 +144,13 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) replaceFunction = haystackString -> { for (Pattern pattern : patterns) { Matcher matcher = pattern.matcher(haystackString); - if (replaceFirst) { - haystackString = matcher.replaceFirst(replacement); - } else { - haystackString = matcher.replaceAll(replacement); - } + try { // Throws IndexOutOfBounds on improper use of regex groups in replacement + if (replaceFirst) { + haystackString = matcher.replaceFirst(replacement); + } else { + haystackString = matcher.replaceAll(replacement); + } + } catch (IndexOutOfBoundsException ignored) {} } return haystackString; }; diff --git a/src/main/java/ch/njol/skript/effects/EffSendBlockChange.java b/src/main/java/ch/njol/skript/effects/EffSendBlockChange.java index e781b5a2a..16d3c968b 100644 --- a/src/main/java/ch/njol/skript/effects/EffSendBlockChange.java +++ b/src/main/java/ch/njol/skript/effects/EffSendBlockChange.java @@ -31,7 +31,7 @@ public class EffSendBlockChange extends Effect { static { Skript.registerEffect(EffSendBlockChange.class, "make %players% see %locations% as %itemtype/blockdata%", - "make %players% see %locations% as [the|its] (original|normal|actual) [block]" + "make %players% see %locations% as [the|its|their] (original|normal|actual) [block[s]]" ); } diff --git a/src/main/java/ch/njol/skript/effects/EffTree.java b/src/main/java/ch/njol/skript/effects/EffTree.java index 71f141eef..8b0204548 100644 --- a/src/main/java/ch/njol/skript/effects/EffTree.java +++ b/src/main/java/ch/njol/skript/effects/EffTree.java @@ -13,7 +13,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Direction; -import ch.njol.skript.util.StructureType; +import ch.njol.skript.util.TreeSpecies; import ch.njol.util.Kleenean; /** @@ -28,26 +28,26 @@ public class EffTree extends Effect { static { Skript.registerEffect(EffTree.class, - "(grow|create|generate) tree [of type %structuretype%] %directions% %locations%", - "(grow|create|generate) %structuretype% %directions% %locations%"); + "(grow|create|generate) tree [of type %treetype%] %directions% %locations%", + "(grow|create|generate) %treetype% %directions% %locations%"); } @SuppressWarnings("null") private Expression blocks; @SuppressWarnings("null") - private Expression type; + private Expression type; @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - type = (Expression) exprs[0]; + type = (Expression) exprs[0]; blocks = Direction.combine((Expression) exprs[1], (Expression) exprs[2]); return true; } @Override public void execute(final Event e) { - final StructureType type = this.type.getSingle(e); + final TreeSpecies type = this.type.getSingle(e); if (type == null) return; for (final Location l : blocks.getArray(e)) { diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index ebd6a110d..9f43c2769 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -255,6 +255,10 @@ public static void addSuperEntity(String codeName, Class entit addSimpleEntity("parched", Parched.class); } + if (Skript.isRunningMinecraft(26, 2)) { + addSimpleEntity("sulfur cube", SulfurCube.class); + } + // SuperTypes addSuperEntity("human", HumanEntity.class); addSuperEntity("damageable", Damageable.class); diff --git a/src/main/java/ch/njol/skript/events/EvtGameMode.java b/src/main/java/ch/njol/skript/events/EvtGameMode.java deleted file mode 100644 index 4ceb7f0c4..000000000 --- a/src/main/java/ch/njol/skript/events/EvtGameMode.java +++ /dev/null @@ -1,50 +0,0 @@ -package ch.njol.skript.events; - -import java.util.Locale; -import java.util.function.Predicate; - -import org.bukkit.GameMode; -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerGameModeChangeEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; - -/** - * @author Peter Güttinger - */ -public final class EvtGameMode extends SkriptEvent { - static { - Skript.registerEvent("Gamemode Change", EvtGameMode.class, PlayerGameModeChangeEvent.class, "game[ ]mode change [to %gamemode%]") - .description("Called when a player's gamemode changes.") - .examples("on gamemode change:", "on gamemode change to adventure:") - .since("1.0"); - } - - @Nullable - private Literal mode; - - @SuppressWarnings("unchecked") - @Override - public boolean init(final Literal[] args, final int matchedPattern, final ParseResult parser) { - mode = (Literal) args[0]; - return true; - } - - @Override - public boolean check(final Event e) { - if (mode != null) { - return mode.check(e, m -> ((PlayerGameModeChangeEvent) e).getNewGameMode().equals(m)); - } - return true; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "gamemode change" + (mode != null ? " to " + mode.toString().toLowerCase(Locale.ENGLISH) : ""); - } - -} diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index f5bdc5a66..0a33e7324 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -7,7 +7,7 @@ import ch.njol.skript.lang.LiteralList; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.StructureType; +import ch.njol.skript.util.TreeSpecies; import ch.njol.util.coll.CollectionUtils; import org.bukkit.Material; import org.bukkit.TreeType; @@ -32,10 +32,10 @@ public class EvtGrow extends SkriptEvent { static { Skript.registerEvent("Grow", EvtGrow.class, CollectionUtils.array(StructureGrowEvent.class, BlockGrowEvent.class), - "grow[th] [of (1:%-structuretypes%|2:%-itemtypes/blockdatas%)]", + "grow[th] [of (1:%-treetypes%|2:%-itemtypes/blockdatas%)]", "grow[th] from %itemtypes/blockdatas%", - "grow[th] [in]to (1:%structuretypes%|2:%itemtypes/blockdatas%)", - "grow[th] from %itemtypes/blockdatas% [in]to (1:%structuretypes%|2:%itemtypes/blockdatas%)" + "grow[th] [in]to (1:%treetypes%|2:%itemtypes/blockdatas%)", + "grow[th] from %itemtypes/blockdatas% [in]to (1:%treetypes%|2:%itemtypes/blockdatas%)" ) .description( "Called when a tree, giant mushroom or plant grows to next stage.", @@ -174,8 +174,8 @@ private static boolean checkTo(Event event, Literal types) { if (event instanceof StructureGrowEvent) { TreeType species = ((StructureGrowEvent) event).getSpecies(); return types.check(event, type -> { - if (type instanceof StructureType) { - return ((StructureType) type).is(species); + if (type instanceof TreeSpecies) { + return ((TreeSpecies) type).is(species); } return false; }); diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 2819ede65..c06ba4404 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -678,42 +678,6 @@ public class SimpleEvents { .examples("on bat toggle sleep:") .since("2.11"); - // WorldBorder Events - if (Skript.classExists("io.papermc.paper.event.world.border.WorldBorderEvent")) { - Skript.registerEvent("World Border Bounds Change", SimpleEvent.class, WorldBorderBoundsChangeEvent.class, "world[ ]border [bounds] chang(e|ing)") - .description( - "Called when a world border changes its bounds, either over time, or instantly.", - "This event does not get called for virtual borders." - ) - .examples( - "on worldborder bounds change:", - "\tbroadcast \"The diameter of %event-worldborder% is changing from %past event-number% to %event-number% over the next %event-timespan%\"" - ) - .since("2.11"); - - Skript.registerEvent("World Border Bounds Finish Change", SimpleEvent.class, WorldBorderBoundsChangeFinishEvent.class, "world[ ]border [bounds] finish chang(e|ing)") - .description( - "Called when a moving world border has finished its move.", - "This event does not get called for virtual borders." - ) - .examples( - "on worldborder bounds finish change:", - "\tbroadcast \"Over the past %event-timespan%, the diameter of %event-worldborder% went from %past event-number% to %event-number%\"" - ) - .since("2.11"); - - Skript.registerEvent("World Border Center Change", SimpleEvent.class, WorldBorderCenterChangeEvent.class, "world[ ]border center chang(e|ing)") - .description( - "Called when a world border's center has changed.", - "This event does not get called for virtual borders." - ) - .examples( - "on worldborder center change:", - "\tbroadcast \"The center of %event-worldborder% has moved from %past event-location% to %event-location%\"" - ) - .since("2.11"); - } - if (Skript.classExists("org.bukkit.event.block.VaultDisplayItemEvent")) { Skript.registerEvent("Vault Display Item", SimpleEvent.class, VaultDisplayItemEvent.class, "vault display[ing] item") diff --git a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java b/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java deleted file mode 100644 index 85885ddcc..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java +++ /dev/null @@ -1,120 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.lang.EventRestrictedSyntax; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Applied Enchantments") -@Description({"The applied enchantments in an enchant event.", - " Deleting or removing the applied enchantments will prevent the item's enchantment."}) -@Example(""" - on enchant: - set the applied enchantments to sharpness 10 and fire aspect 5 - """) -@Events("enchant") -@Since("2.5") -public class ExprAppliedEnchantments extends SimpleExpression implements EventRestrictedSyntax { - - static { - Skript.registerExpression(ExprAppliedEnchantments.class, EnchantmentType.class, ExpressionType.SIMPLE, "[the] applied enchant[ment]s"); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class); - } - - @SuppressWarnings("null") - @Override - @Nullable - protected EnchantmentType[] get(Event e) { - if (!(e instanceof EnchantItemEvent)) - return null; - - return ((EnchantItemEvent) e).getEnchantsToAdd().entrySet().stream() - .map(entry -> new EnchantmentType(entry.getKey(), entry.getValue())) - .toArray(EnchantmentType[]::new); - } - - @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.REMOVE_ALL || mode == ChangeMode.RESET) - return null; - return CollectionUtils.array(Enchantment[].class, EnchantmentType[].class); - } - - @SuppressWarnings("null") - @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (!(event instanceof EnchantItemEvent)) - return; - - EnchantmentType[] enchants = new EnchantmentType[delta != null ? delta.length : 0]; - if (delta != null && delta.length != 0) { - for (int i = 0; i < delta.length; i++) { - if (delta[i] instanceof EnchantmentType) - enchants[i] = (EnchantmentType) delta[i]; - else - enchants[i] = new EnchantmentType((Enchantment) delta[i]); - } - } - EnchantItemEvent e = (EnchantItemEvent) event; - switch (mode) { - case SET: - e.getEnchantsToAdd().clear(); - case ADD: - for (EnchantmentType enchant : enchants) - e.getEnchantsToAdd().put(enchant.getType(), enchant.getLevel()); - break; - case REMOVE: - for (EnchantmentType enchant : enchants) - e.getEnchantsToAdd().remove(enchant.getType(), enchant.getLevel()); - break; - case DELETE: - e.getEnchantsToAdd().clear(); - case REMOVE_ALL: - case RESET: - assert false; - } - } - - @Override - public boolean isSingle() { - return false; - } - - @Override - public Class getReturnType() { - return EnchantmentType.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return "applied enchantments"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java index 7b773e1f6..7f72daff4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java @@ -35,9 +35,6 @@ public class ExprAttachedBlock extends PropertyExpression { register(ExprAttachedBlock.class, Block.class, "(attached|hit) block[multiple:s]", "projectiles"); } - // TODO - remove this when only Paper 1.21.4+ is supported - private static final boolean SUPPORTS_MULTIPLE = Skript.methodExists(AbstractArrow.class, "getAttachedBlocks"); - private boolean isMultiple; @Override @@ -46,12 +43,7 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is // noinspection unchecked setExpr((Expression) expressions[0]); - if (!SUPPORTS_MULTIPLE && isMultiple) { - Skript.error("The plural version of this expression is only available when running Paper 1.21.4 or newer."); - return false; - } - - if (SUPPORTS_MULTIPLE && !isMultiple) { + if (!isMultiple) { isMultiple = true; String expr = toString(null, Skript.debug()); isMultiple = false; diff --git a/src/main/java/ch/njol/skript/expressions/ExprElement.java b/src/main/java/ch/njol/skript/expressions/ExprElement.java index 0d1bb8fdc..623616819 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprElement.java +++ b/src/main/java/ch/njol/skript/expressions/ExprElement.java @@ -17,6 +17,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EmptyIterator; import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.function.TriFunction; import org.bukkit.event.Event; @@ -29,6 +30,7 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.IntStream; @Name("Elements") @Description({ @@ -50,6 +52,7 @@ public class ExprElement extends SimpleExpression implements KeyProviderEx {"[the] (first|1:last) element [out] (of|in) %objects%", new ElementType[]{ElementType.FIRST_ELEMENT, ElementType.LAST_ELEMENT}}, {"[the] (first|1:last) %integer% elements [out] (of|in) %objects%", new ElementType[]{ElementType.FIRST_X_ELEMENTS, ElementType.LAST_X_ELEMENTS}}, {"[a] random element [out] (of|in) %objects%", new ElementType[]{ElementType.RANDOM}}, + {"%integer% random [:distinct] elements [out] (of|in) %objects%", new ElementType[]{ElementType.RANDOM_ELEMENTS, ElementType.RANDOM_DISTINCT_ELEMENTS}}, {"[the] %integer%(st|nd|rd|th) [1:[to] last] element [out] (of|in) %objects%", new ElementType[]{ElementType.ORDINAL, ElementType.TAIL_END_ORDINAL}}, {"[the] elements (from|between) %integer% (to|and) %integer% [out] (of|in) %objects%", new ElementType[]{ElementType.RANGE}}, }); @@ -75,6 +78,26 @@ private enum ElementType { Object element = CollectionUtils.getRandom(array); return Iterators.singletonIterator(element); }), + RANDOM_ELEMENTS((iterator, amount) -> { + Object[] array = Iterators.toArray(iterator, Object.class); + if (array.length == 0) + return EmptyIterator.get(); + return IntStream.range(0, amount).mapToObj(ignored -> CollectionUtils.getRandom(array)).iterator(); + }), + RANDOM_DISTINCT_ELEMENTS((iterator, amount) -> { + List remaining = Lists.newArrayList(iterator); + if (remaining.isEmpty()) + return EmptyIterator.get(); + Random random = ThreadLocalRandom.current(); + return IntStream.range(0, Math.min(amount, remaining.size())) + .mapToObj(ignored -> { + int index = random.nextInt(remaining.size()); + Object element = remaining.get(index); + remaining.remove(index); + return element; + }) + .iterator(); + }), ORDINAL((iterator, index) -> { Iterators.advance(iterator, index - 1); if (!iterator.hasNext()) @@ -138,13 +161,17 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is switch (type = types[parseResult.mark]) { case RANGE: endIndex = (Expression) expressions[1]; - case FIRST_X_ELEMENTS, LAST_X_ELEMENTS, ORDINAL, TAIL_END_ORDINAL: + case FIRST_X_ELEMENTS, LAST_X_ELEMENTS, RANDOM_ELEMENTS, ORDINAL, TAIL_END_ORDINAL: startIndex = (Expression) expressions[0]; break; default: startIndex = null; break; } + + if (type == ElementType.RANDOM_ELEMENTS && parseResult.hasTag("distinct")) + this.type = ElementType.RANDOM_DISTINCT_ELEMENTS; + this.keyed = KeyProviderExpression.canReturnKeys(this.expr); return LiteralUtils.canInitSafely(expr); } @@ -242,8 +269,17 @@ private Iterator transformIterator(Event event, @Nullable Iterator ite return switch (type) { case FIRST_ELEMENT -> CollectionUtils.array((T) queue.pollFirst()); case LAST_ELEMENT -> CollectionUtils.array((T) queue.pollLast()); - case RANDOM -> - CollectionUtils.array((T) queue.removeSafely(ThreadLocalRandom.current().nextInt(0, queue.size()))); + case RANDOM -> { + Random random = ThreadLocalRandom.current(); + yield CollectionUtils.array((T) queue.removeSafely(random.nextInt(0, queue.size()))); + } + case RANDOM_ELEMENTS, RANDOM_DISTINCT_ELEMENTS -> { + Random random = ThreadLocalRandom.current(); + T[] elements = (T[]) Array.newInstance(getReturnType(), Math.min(startIndex, queue.size())); + for (int i = 0; i < elements.length; i++) + elements[i] = (T) queue.removeSafely(random.nextInt(0, queue.size())); + yield elements; + } case ORDINAL -> CollectionUtils.array((T) queue.removeSafely(startIndex - 1)); case TAIL_END_ORDINAL -> CollectionUtils.array((T) queue.removeSafely(queue.size() - startIndex)); case FIRST_X_ELEMENTS -> CollectionUtils.array((T[]) queue.removeRangeSafely(0, startIndex)); @@ -291,7 +327,10 @@ public Expression getConvertedExpression(Class... to) { @Override public boolean isSingle() { - return type != ElementType.FIRST_X_ELEMENTS && type != ElementType.LAST_X_ELEMENTS && type != ElementType.RANGE; + return switch (type) { + case FIRST_X_ELEMENTS, LAST_X_ELEMENTS, RANDOM_ELEMENTS, RANDOM_DISTINCT_ELEMENTS, RANGE -> false; + default -> true; + }; } @Override @@ -345,6 +384,14 @@ public String toString(@Nullable Event event, boolean debug) { case RANDOM: prefix = "a random"; break; + case RANDOM_ELEMENTS: + assert startIndex != null; + prefix = startIndex.toString(event, debug) + " random"; + break; + case RANDOM_DISTINCT_ELEMENTS: + assert startIndex != null; + prefix = startIndex.toString(event, debug) + " distinct random"; + break; case ORDINAL: case TAIL_END_ORDINAL: assert startIndex != null; diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java deleted file mode 100644 index b76aeccad..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java +++ /dev/null @@ -1,116 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.lang.EventRestrictedSyntax; -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.bukkit.event.enchantment.PrepareItemEnchantEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Enchant Item") -@Description({"The enchant item in an enchant prepare event or enchant event.", - "It can be modified, but enchantments will still be applied in the enchant event."}) -@Example(""" - on enchant: - set the enchanted item to a diamond chestplate - """) -@Example(""" - on enchant prepare: - set the enchant item to a wooden sword - """) -@Events({"enchant prepare", "enchant"}) -@Since("2.5") -public class ExprEnchantItem extends SimpleExpression implements EventRestrictedSyntax { - - static { - Skript.registerExpression(ExprEnchantItem.class, ItemType.class, ExpressionType.SIMPLE, "[the] enchant[ed] item"); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class, PrepareItemEnchantEvent.class); - } - - @Override - @Nullable - protected ItemType[] get(Event e) { - if (e instanceof PrepareItemEnchantEvent) - return new ItemType[]{new ItemType(((PrepareItemEnchantEvent) e).getItem())}; - else if (e instanceof EnchantItemEvent) - return new ItemType[]{new ItemType(((EnchantItemEvent) e).getItem())}; - else - return null; - } - - @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET) - return CollectionUtils.array(ItemType.class); - return null; - } - - @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null) - return; - ItemType item = ((ItemType) delta[0]); - switch (mode) { - case SET: - if (event instanceof PrepareItemEnchantEvent) { - PrepareItemEnchantEvent e = (PrepareItemEnchantEvent) event; - e.getItem().setType(item.getMaterial()); - e.getItem().setItemMeta(item.getItemMeta()); - e.getItem().setAmount(item.getAmount()); - } else if (event instanceof EnchantItemEvent) { - EnchantItemEvent e = (EnchantItemEvent) event; - e.getItem().setType(item.getMaterial()); - e.getItem().setItemMeta(item.getItemMeta()); - e.getItem().setAmount(item.getAmount()); - } - break; - case ADD: - case REMOVE: - case RESET: - case DELETE: - case REMOVE_ALL: - assert false; - } - } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public Class getReturnType() { - return ItemType.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return "enchanted item"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java deleted file mode 100644 index 97d0d4f2f..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java +++ /dev/null @@ -1,105 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.lang.EventRestrictedSyntax; -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.Experience; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Enchanting Experience Cost") -@Description({"The cost of enchanting in an enchant event.", - "This is number that was displayed in the enchantment table, not the actual number of levels removed."}) -@Example(""" - on enchant: - send "Cost: %the displayed enchanting cost%" to player - """) -@Events("enchant") -@Since("2.5") -public class ExprEnchantingExpCost extends SimpleExpression implements EventRestrictedSyntax { - - static { - Skript.registerExpression(ExprEnchantingExpCost.class, Long.class, ExpressionType.SIMPLE, - "[the] [displayed] ([e]xp[erience]|enchanting) cost"); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class); - } - - @Override - @Nullable - protected Long[] get(Event e) { - return new Long[]{(long) ((EnchantItemEvent) e).getExpLevelCost()}; - } - - @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.RESET || mode == ChangeMode.DELETE || mode == ChangeMode.REMOVE_ALL) - return null; - return CollectionUtils.array(Number.class, Experience.class); - } - - @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null) - return; - Object c = delta[0]; - int cost = c instanceof Number ? ((Number) c).intValue() : ((Experience) c).getXP(); - EnchantItemEvent e = (EnchantItemEvent) event; - switch (mode) { - case SET: - e.setExpLevelCost(cost); - break; - case ADD: - int add = e.getExpLevelCost() + cost; - e.setExpLevelCost(add); - break; - case REMOVE: - int subtract = e.getExpLevelCost() - cost; - e.setExpLevelCost(subtract); - break; - case RESET: - case DELETE: - case REMOVE_ALL: - assert false; - } - } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public Class getReturnType() { - return Long.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return "the displayed cost of enchanting"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java deleted file mode 100644 index 42b53e166..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java +++ /dev/null @@ -1,68 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.PrepareItemEnchantEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.util.Kleenean; - -@Name("Enchantment Bonus") -@Description("The enchantment bonus in an enchant prepare event. This represents the number of bookshelves affecting/surrounding the enchantment table.") -@Example(""" - on enchant: - send "There are %enchantment bonus% bookshelves surrounding this enchantment table!" to player - """) -@Events("enchant prepare") -@Since("2.5") -public class ExprEnchantmentBonus extends SimpleExpression implements EventRestrictedSyntax { - - static { - Skript.registerExpression(ExprEnchantmentBonus.class, Long.class, ExpressionType.SIMPLE, "[the] enchantment bonus"); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PrepareItemEnchantEvent.class); - } - - @Override - @Nullable - protected Long[] get(Event e) { - return new Long[]{(long) ((PrepareItemEnchantEvent) e).getEnchantmentBonus()}; - } - - @Override - public boolean isSingle() { - return true; - } - - - @Override - public Class getReturnType() { - return Long.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return "enchantment bonus"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java deleted file mode 100644 index d574d0c8e..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java +++ /dev/null @@ -1,198 +0,0 @@ -package ch.njol.skript.expressions; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import ch.njol.skript.lang.EventRestrictedSyntax; -import org.bukkit.enchantments.EnchantmentOffer; -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.PrepareItemEnchantEvent; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Enchantment Offer") -@Description("The enchantment offer in enchant prepare events.") -@Example(""" - on enchant prepare: - send "Your enchantment offers are: %the enchantment offers%" to player - """) -@Since("2.5") -@Events("enchant prepare") -@RequiredPlugins("1.11 or newer") -public class ExprEnchantmentOffer extends SimpleExpression implements EventRestrictedSyntax { - - static { - if (Skript.classExists("org.bukkit.enchantments.EnchantmentOffer")) { - Skript.registerExpression(ExprEnchantmentOffer.class, EnchantmentOffer.class, ExpressionType.SIMPLE, - "[all [of]] [the] enchant[ment] offers", - "enchant[ment] offer[s] %numbers%", - "[the] %number%(st|nd|rd|th) enchant[ment] offer"); - } - } - - @SuppressWarnings("null") - private Expression exprOfferNumber; - - private boolean all; - - // Used for getCost() - private final Random rand = new Random(); - - @SuppressWarnings({"null", "unchecked"}) - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (matchedPattern == 0) { - all = true; - } else { - exprOfferNumber = (Expression) exprs[0]; - all = false; - } - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PrepareItemEnchantEvent.class); - } - - @SuppressWarnings({"null", "unused"}) - @Override - @Nullable - protected EnchantmentOffer[] get(Event e) { - if (!(e instanceof PrepareItemEnchantEvent)) - return null; - - if (all) - return ((PrepareItemEnchantEvent) e).getOffers(); - if (exprOfferNumber == null) - return new EnchantmentOffer[0]; - if (exprOfferNumber.isSingle()) { - Number offerNumber = exprOfferNumber.getSingle(e); - if (offerNumber == null) - return new EnchantmentOffer[0]; - int offer = offerNumber.intValue(); - if (offer < 1 || offer > ((PrepareItemEnchantEvent) e).getOffers().length) - return new EnchantmentOffer[0]; - return new EnchantmentOffer[]{((PrepareItemEnchantEvent) e).getOffers()[offer - 1]}; - } - List offers = new ArrayList<>(); - int i; - for (Number n : exprOfferNumber.getArray(e)) { - i = n.intValue(); - if (i >= 1 || i <= ((PrepareItemEnchantEvent) e).getOffers().length) - offers.add(((PrepareItemEnchantEvent) e).getOffers()[i - 1]); - } - return offers.toArray(new EnchantmentOffer[0]); - } - - @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) - return CollectionUtils.array(EnchantmentType.class); - return null; - } - - @SuppressWarnings("null") - @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null && mode != ChangeMode.DELETE) - return; - EnchantmentType et = mode != ChangeMode.DELETE ? (EnchantmentType) delta[0] : null; - if (event instanceof PrepareItemEnchantEvent) { - PrepareItemEnchantEvent e = (PrepareItemEnchantEvent) event; - switch (mode) { - case SET: - if (all) { - for (int i = 0; i <= 2; i++) { - EnchantmentOffer eo = e.getOffers()[i]; - if (eo == null) { - eo = new EnchantmentOffer(et.getType(), et.getLevel(), getCost(i + 1, e.getEnchantmentBonus())); - e.getOffers()[i] = eo; - } else { - eo.setEnchantment(et.getType()); - eo.setEnchantmentLevel(et.getLevel()); - } - } - } else { - for (Number n : exprOfferNumber.getArray(e)) { - int slot = n.intValue() - 1; - EnchantmentOffer eo = e.getOffers()[slot]; - if (eo == null) { - eo = new EnchantmentOffer(et.getType(), et.getLevel(), getCost(slot + 1, e.getEnchantmentBonus())); - e.getOffers()[slot] = eo; - } else { - eo.setEnchantment(et.getType()); - eo.setEnchantmentLevel(et.getLevel()); - } - } - } - break; - case DELETE: - if (all) { - Arrays.fill(e.getOffers(), null); - } else { - for (Number n : exprOfferNumber.getArray(e)) - e.getOffers()[n.intValue() - 1] = null; - } - break; - case ADD: - case REMOVE: - case RESET: - case REMOVE_ALL: - assert false; - } - } - } - - @Override - public boolean isSingle() { - return !all && exprOfferNumber.isSingle(); - } - - @Override - public Class getReturnType() { - return EnchantmentOffer.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return all ? "the enchantment offers" : "enchantment offer(s) " + exprOfferNumber.toString(e, debug); - } - - /** - * Returns an enchantment cost from an enchantment button and number of bookshelves. - * @param slot The enchantment button slot (1, 2, or 3). - * @param bookshelves The number of bookshelves around the enchantment table. - * @return A cost for that enchantment button with the number of bookshelves, or 1 if 'slot' is not an integer from 1 to 3. - */ - public int getCost(int slot, int bookshelves) { - // (from 1 to 8) + floor(bookshelves / 2) + (from 0 to bookshelves) - int base = (int) ((rand.nextInt(7) + 1) + Math.floor(bookshelves / 2) + (rand.nextInt(bookshelves + 1))); - switch (slot) { - case 1: return Math.max(base / 3, 1); - case 2: return (base * 2) / 3 + 1; - case 3: return Math.max(base, bookshelves * 2); - default: return 1; - } - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java b/src/main/java/ch/njol/skript/expressions/ExprGameMode.java deleted file mode 100644 index 3de7356da..000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java +++ /dev/null @@ -1,88 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.effects.Delay; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerGameModeChangeEvent; -import org.jetbrains.annotations.Nullable; - -/** - * @author Peter Güttinger - */ -@Name("Game Mode") -@Description("The gamemode of a player. (Gamemodes)") -@Example("player's gamemode is survival") -@Example("set the player's gamemode to creative") -@Since("1.0") -public class ExprGameMode extends PropertyExpression { - - static { - Skript.registerExpression(ExprGameMode.class, GameMode.class, ExpressionType.PROPERTY, "[the] game[ ]mode of %players%", "%players%'[s] game[ ]mode"); - } - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - setExpr((Expression) vars[0]); - return true; - } - - @Override - protected GameMode[] get(final Event e, final Player[] source) { - return get(source, player -> { - if (getTime() >= 0 && e instanceof PlayerGameModeChangeEvent && ((PlayerGameModeChangeEvent) e).getPlayer() == player && !Delay.isDelayed(e)) - return ((PlayerGameModeChangeEvent) e).getNewGameMode(); - return player.getGameMode(); - }); - } - - @Override - public Class getReturnType() { - return GameMode.class; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the gamemode of " + getExpr().toString(e, debug); - } - - @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.RESET) - return CollectionUtils.array(GameMode.class); - return null; - } - - @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { - final GameMode m = delta == null ? Bukkit.getDefaultGameMode() : (GameMode) delta[0]; - for (final Player p : getExpr().getArray(e)) { - if (getTime() >= 0 && e instanceof PlayerGameModeChangeEvent && ((PlayerGameModeChangeEvent) e).getPlayer() == p && !Delay.isDelayed(e)) { - if (((PlayerGameModeChangeEvent) e).getNewGameMode() != m) - ((PlayerGameModeChangeEvent) e).setCancelled(true); - } - p.setGameMode(m); - } - } - - @Override - public boolean setTime(final int time) { - return super.setTime(time, PlayerGameModeChangeEvent.class); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java index 651fa6e4f..ee0f46e90 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java @@ -31,12 +31,12 @@ @Since("2.2-dev34, 2.5 (slots)") public class ExprInventoryInfo extends SimpleExpression { - private final static int HOLDER = 1, VIEWERS = 2, ROWS = 3, SLOTS = 4; + private final static int HOLDER = 1, ROWS = 2, SLOTS = 3; static { Skript.registerExpression(ExprInventoryInfo.class, Object.class, ExpressionType.PROPERTY, - "(" + HOLDER + "¦holder[s]|" + VIEWERS + "¦viewers|" + ROWS + "¦[amount of] rows|" + SLOTS + "¦[amount of] slots)" + " of %inventories%", - "%inventories%'[s] (" + HOLDER + "¦holder[s]|" + VIEWERS + "¦viewers|" + ROWS + "¦[amount of] rows|" + SLOTS + "¦[amount of] slots)"); + "(" + HOLDER + "¦holder[s]|" + ROWS + "¦[amount of] rows|" + SLOTS + "¦[amount of] slots)" + " of %inventories%", + "%inventories%'[s] (" + HOLDER + "¦holder[s]|" + ROWS + "¦[amount of] rows|" + SLOTS + "¦[amount of] slots)"); } @SuppressWarnings("null") @@ -78,12 +78,6 @@ protected Object[] get(Event event) { sizes.add(inventory.getSize()); } return sizes.toArray(new Number[0]); - case VIEWERS: - List viewers = new ArrayList<>(); - for (Inventory inventory : inventories) { - viewers.addAll(inventory.getViewers()); - } - return viewers.stream().filter(viewer -> viewer instanceof Player).toArray(Player[]::new); default: return (Object[]) Array.newInstance(getReturnType(), 0); } @@ -91,17 +85,19 @@ protected Object[] get(Event event) { @Override public boolean isSingle() { - return inventories.isSingle() && type != VIEWERS; + return inventories.isSingle(); } @Override public Class getReturnType() { - return type == HOLDER ? InventoryHolder.class : (type == ROWS || type == SLOTS) ? Number.class : Player.class; + if (type == HOLDER) + return InventoryHolder.class; + return Number.class; } @Override public String toString(@Nullable Event e, boolean debug) { - return (type == HOLDER ? "holder of " : type == ROWS ? "rows of " : type == SLOTS ? "slots of " : "viewers of ") + inventories.toString(e, debug); + return (type == HOLDER ? "holder of " : type == ROWS ? "rows of " : "slots of ") + inventories.toString(e, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java index c962c6b14..5a2de5581 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLeashHolder.java @@ -1,7 +1,6 @@ package ch.njol.skript.expressions; import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; import org.jetbrains.annotations.Nullable; import ch.njol.skript.doc.Description; @@ -9,21 +8,24 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import io.papermc.paper.entity.Leashable; @Name("Leash Holder") -@Description("The leash holder of a living entity.") +@Description("The leash holder of an entity.") @Example("set {_example} to the leash holder of the target mob") -@Since("2.3") -public class ExprLeashHolder extends SimplePropertyExpression { +@Since("2.3, 2.16 (all leashable entities)") +public class ExprLeashHolder extends SimplePropertyExpression { static { - register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "livingentities"); + register(ExprLeashHolder.class, Entity.class, "leash holder[s]", "entities"); } @Override - @Nullable - public Entity convert(LivingEntity entity) { - return entity.isLeashed() ? entity.getLeashHolder() : null; + public @Nullable Entity convert(Entity entity) { + if (entity instanceof Leashable leashable) { + return leashable.isLeashed() ? leashable.getLeashHolder() : null; + } + return null; } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprNamed.java b/src/main/java/ch/njol/skript/expressions/ExprNamed.java index debc5a889..e01ccaa3b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprNamed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprNamed.java @@ -68,16 +68,19 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Object[] get(Event event, Object[] source) { Component name = this.name.getSingle(event); - if (name == null) { - return get(source, obj -> obj); // No name provided, do nothing - } return get(source, object -> { if (object instanceof InventoryType inventoryType) { if (!inventoryType.isCreatable()) { return null; } + if (name == null) { + return Bukkit.createInventory(null, inventoryType); + } return Bukkit.createInventory(null, inventoryType, name); } else { + if (name == null) { + return object; + } ItemType item = (ItemType) object; item = item.clone(); ItemMeta meta = item.getItemMeta(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprUUID.java b/src/main/java/ch/njol/skript/expressions/ExprUUID.java index bc7ff9727..0cf8abd0f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUUID.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUUID.java @@ -36,7 +36,7 @@ public class ExprUUID extends SimplePropertyExpression { static { - register(ExprUUID.class, UUID.class, "UUID", "offlineplayers/worlds/entities"); + register(ExprUUID.class, UUID.class, "uuid[s]", "offlineplayers/worlds/entities"); } @Override diff --git a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java index 985c42ab2..49bac8611 100644 --- a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java +++ b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java @@ -49,6 +49,16 @@ public TriggerItem setNext(@Nullable TriggerItem next) { return effectSection.getNext(); } + @Override + public @Nullable TriggerItem getActualNext() { + return effectSection.getActualNext(); + } + + @Override + public @Nullable ExecutionIntent executionIntent() { + return effectSection.executionIntent(); + } + @Override public String toString(@Nullable Event event, boolean debug) { return effectSection.toString(event, debug); diff --git a/src/main/java/ch/njol/skript/lang/util/SectionUtils.java b/src/main/java/ch/njol/skript/lang/util/SectionUtils.java index cfeb86e30..2d34a4db6 100644 --- a/src/main/java/ch/njol/skript/lang/util/SectionUtils.java +++ b/src/main/java/ch/njol/skript/lang/util/SectionUtils.java @@ -46,9 +46,29 @@ private SectionUtils() { } */ @SuppressWarnings("JavadocReference") public static @Nullable Trigger loadLinkedCode(String name, BiFunction triggerSupplier) { + return loadLinkedCode(name, false, triggerSupplier); + } + + /** + * A variant of {@link #loadLinkedCode(String, BiFunction)} that permits delays within the loaded trigger. + * Type hints are still shared with the outer section, but delays inside the body do not cause a parse-time error. + * The caller is responsible for correctly handling any delays encountered while executing the returned trigger + * (e.g. by scheduling execution asynchronously or on a separate tick). + * @see #loadLinkedCode(String, BiFunction) + * @param name The name of the section being loaded. + * @param triggerSupplier A function to load code using a trigger. See {@link #loadLinkedCode(String, BiFunction)} + * for details on the expected behavior of this supplier. + * @return The result of {@code triggerSupplier}, or null if some issue occurred. + */ + public static @Nullable Trigger loadDelayableLinkedCode(String name, BiFunction triggerSupplier) { + return loadLinkedCode(name, true, triggerSupplier); + } + + private static @Nullable Trigger loadLinkedCode(String name, boolean allowDelays, + BiFunction triggerSupplier) { AtomicBoolean delayed = new AtomicBoolean(false); AtomicReference hintBackup = new AtomicReference<>(); - // Copy hints and ensure no delays + // Copy hints and record whether the body was delayed Runnable beforeLoading = () -> ParserInstance.get().getHintManager().enterScope(false); Runnable afterLoading = () -> { ParserInstance parser = ParserInstance.get(); @@ -60,7 +80,7 @@ private SectionUtils() { } Trigger trigger = triggerSupplier.apply(beforeLoading, afterLoading); - if (delayed.get()) { + if (!allowDelays && delayed.get()) { Skript.error("Delays can't be used within a '" + name + "' section"); return null; } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java b/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java index 5f2663889..a5eead095 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java @@ -9,12 +9,27 @@ /** * A very basic SkriptEvent which returns true for all events (i.e. all registered events). - * - * @author Peter Güttinger */ public class SimpleEvent extends SkriptEvent { - public SimpleEvent() {} + private final String toString; + + /** + * Creates a new SimpleEvent with the default toString of 'simple event'. + * Prefer using {@link #SimpleEvent(String)} to provide a more descriptive toString. + */ + public SimpleEvent() { + this("simple event"); + } + + /** + * Creates a new SimpleEvent with a custom toString. + * + * @param string the string to return in toString + */ + public SimpleEvent(String string) { + this.toString = string; + } @Override public boolean check(Event event) { @@ -30,7 +45,7 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parser) { @Override public String toString(@Nullable Event event, boolean debug) { - return "simple event"; + return toString; } } diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java index 681ba00f2..39494c7b8 100644 --- a/src/main/java/ch/njol/skript/structures/StructAliases.java +++ b/src/main/java/ch/njol/skript/structures/StructAliases.java @@ -37,7 +37,7 @@ public class StructAliases extends Structure { public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult, @Nullable EntryContainer entryContainer) { // noinspection ConstantConditions - entry container cannot be null as this structure is not simple SectionNode node = entryContainer.getSource(); - node.convertToEntries(0, "="); + node.convertToEntries(1, "="); // Initialize and load script aliases Script script = getParser().getCurrentScript(); diff --git a/src/main/java/ch/njol/skript/util/StructureType.java b/src/main/java/ch/njol/skript/util/StructureType.java deleted file mode 100644 index f15182c78..000000000 --- a/src/main/java/ch/njol/skript/util/StructureType.java +++ /dev/null @@ -1,103 +0,0 @@ -package ch.njol.skript.util; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Pattern; - -import org.bukkit.Location; -import org.bukkit.TreeType; -import org.bukkit.block.Block; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.Noun; -import ch.njol.util.coll.CollectionUtils; - -public enum StructureType { - TREE(TreeType.TREE, TreeType.BIG_TREE, TreeType.REDWOOD, TreeType.TALL_REDWOOD, TreeType.MEGA_REDWOOD, - TreeType.BIRCH, TreeType.TALL_BIRCH, TreeType.SMALL_JUNGLE, TreeType.JUNGLE, TreeType.COCOA_TREE, - TreeType.ACACIA, TreeType.DARK_OAK, TreeType.SWAMP), - - REGULAR(TreeType.TREE, TreeType.BIG_TREE), SMALL_REGULAR(TreeType.TREE), BIG_REGULAR(TreeType.BIG_TREE), - REDWOOD(TreeType.REDWOOD, TreeType.TALL_REDWOOD), SMALL_REDWOOD(TreeType.REDWOOD), BIG_REDWOOD(TreeType.TALL_REDWOOD), - MEGA_REDWOOD(TreeType.MEGA_REDWOOD), - BIRCH(TreeType.BIRCH), TALL_BIRCH(TreeType.TALL_BIRCH), - JUNGLE(TreeType.SMALL_JUNGLE, TreeType.JUNGLE), SMALL_JUNGLE(TreeType.SMALL_JUNGLE), BIG_JUNGLE(TreeType.JUNGLE), - JUNGLE_BUSH(TreeType.JUNGLE_BUSH), COCOA_TREE(TreeType.COCOA_TREE), - ACACIA(TreeType.ACACIA), DARK_OAK(TreeType.DARK_OAK), - SWAMP(TreeType.SWAMP), - - MUSHROOM(TreeType.RED_MUSHROOM, TreeType.BROWN_MUSHROOM), - RED_MUSHROOM(TreeType.RED_MUSHROOM), BROWN_MUSHROOM(TreeType.BROWN_MUSHROOM), - - ; - - private Noun name; - private final TreeType[] types; - - private StructureType(final TreeType... types) { - this.types = types; - name = new Noun("tree types." + name() + ".name"); - } - - public void grow(final Location loc) { - TreeType tree = CollectionUtils.getRandom(types); - assert tree != null; // No enum member causes empty types - loc.getWorld().generateTree(loc, tree); - } - - public void grow(final Block b) { - TreeType tree = CollectionUtils.getRandom(types); - assert tree != null; // No enum member causes empty types - b.getWorld().generateTree(b.getLocation(), tree); - } - - public TreeType[] getTypes() { - return types; - } - - @Override - public String toString() { - return name.toString(); - } - - public String toString(final int flags) { - return name.toString(flags); - } - - public Noun getName() { - return name; - } - - public boolean is(final TreeType type) { - return CollectionUtils.contains(types, type); - } - - /** - * lazy - */ - final static Map parseMap = new HashMap<>(); - - static { - Language.addListener(parseMap::clear); - } - - @Nullable - public static StructureType fromName(String s) { - if (parseMap.isEmpty()) { - for (final StructureType t : values()) { - final String pattern = Language.get("tree types." + t.name() + ".pattern"); - parseMap.put(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE), t); - } - } - s = "" + s.toLowerCase(Locale.ENGLISH); - for (final Entry e : parseMap.entrySet()) { - if (e.getKey().matcher(s).matches()) - return e.getValue(); - } - return null; - } - -} diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index 4236d1f92..976f75b64 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -372,25 +372,42 @@ public Timespan difference(Timespan timespan) { @Override public long get(TemporalUnit unit) { - if (unit instanceof TimePeriod period) - return this.getAs(period); + if (unit instanceof TimePeriod period) { + return getComponent(period); + } if (!(unit instanceof ChronoUnit chrono)) throw new UnsupportedTemporalTypeException("Not a supported temporal unit: " + unit); return switch (chrono) { - case MILLIS -> this.getAs(TimePeriod.MILLISECOND); - case SECONDS -> this.getAs(TimePeriod.SECOND); - case MINUTES -> this.getAs(TimePeriod.MINUTE); - case HOURS -> this.getAs(TimePeriod.HOUR); - case DAYS -> this.getAs(TimePeriod.DAY); - case WEEKS -> this.getAs(TimePeriod.WEEK); - case MONTHS -> this.getAs(TimePeriod.MONTH); - case YEARS -> this.getAs(TimePeriod.YEAR); + case YEARS -> getComponent(TimePeriod.YEAR); + case MONTHS -> getComponent(TimePeriod.MONTH); + case WEEKS -> getComponent(TimePeriod.WEEK); + case DAYS -> getComponent(TimePeriod.DAY); + case HOURS -> getComponent(TimePeriod.HOUR); + case MINUTES -> getComponent(TimePeriod.MINUTE); + case SECONDS -> getComponent(TimePeriod.SECOND); + case MILLIS -> getComponent(TimePeriod.MILLISECOND); default -> throw new UnsupportedTemporalTypeException("Not a supported time unit: " + chrono); }; } + private long getComponent(TimePeriod period) { + if (isInfinite()) { + return Long.MAX_VALUE; + } + + long remainder = this.millis; + + for (TimePeriod p : TimePeriod.values()) { + if (p.ordinal() > period.ordinal()) { + remainder %= p.getTime(); + } + } + + return remainder / period.getTime(); + } + @Override public List getUnits() { return List.of(TimePeriod.values()).reversed(); @@ -413,7 +430,7 @@ public int compareTo(@Nullable Timespan time) { @Override public int hashCode() { - return 31 + (int) (millis / Integer.MAX_VALUE); + return Long.hashCode(millis); } @Override diff --git a/src/main/java/ch/njol/skript/util/TreeSpecies.java b/src/main/java/ch/njol/skript/util/TreeSpecies.java new file mode 100644 index 000000000..12e7707aa --- /dev/null +++ b/src/main/java/ch/njol/skript/util/TreeSpecies.java @@ -0,0 +1,116 @@ +package ch.njol.skript.util; + +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; +import org.bukkit.TreeType; +import org.bukkit.World; +import org.bukkit.block.Block; + +/** + * Repesents a grouping of Bukkkit {@link TreeType TreeTypes} + */ +public enum TreeSpecies { + TREE(TreeType.values()), + + OAK(TreeType.TREE, TreeType.BIG_TREE), + SMALL_OAK(TreeType.TREE), + BIG_OAK(TreeType.BIG_TREE), + + SPRUCE(TreeType.REDWOOD, TreeType.TALL_REDWOOD), + SMALL_SPRUCE(TreeType.REDWOOD), + BIG_SPRUCE(TreeType.TALL_REDWOOD), + MEGA_SPRUCE(TreeType.MEGA_REDWOOD), + + BIRCH(TreeType.BIRCH, TreeType.TALL_BIRCH), + SMALL_BIRCH(TreeType.BIRCH), + TALL_BIRCH(TreeType.TALL_BIRCH), + + JUNGLE(TreeType.SMALL_JUNGLE, TreeType.JUNGLE, TreeType.COCOA_TREE), + SMALL_JUNGLE(TreeType.SMALL_JUNGLE), + BIG_JUNGLE(TreeType.JUNGLE), + COCOA_TREE(TreeType.COCOA_TREE), + + JUNGLE_BUSH(TreeType.JUNGLE_BUSH), + + ACACIA(TreeType.ACACIA), + DARK_OAK(TreeType.DARK_OAK), + SWAMP(TreeType.SWAMP), + + MUSHROOM(TreeType.RED_MUSHROOM, TreeType.BROWN_MUSHROOM), + RED_MUSHROOM(TreeType.RED_MUSHROOM), + BROWN_MUSHROOM(TreeType.BROWN_MUSHROOM), + + MANGROVE(TreeType.MANGROVE, TreeType.TALL_MANGROVE), + SMALL_MANGROVE(TreeType.MANGROVE), + BIG_MANGROVE(TreeType.TALL_MANGROVE), + + AZALEA(TreeType.AZALEA), + + PALE_OAK(TreeType.PALE_OAK, TreeType.PALE_OAK_CREAKING), + PALE_OAK_NORMAL(TreeType.PALE_OAK), + PALE_OAK_CREAKING(TreeType.PALE_OAK_CREAKING), + + CHERRY(TreeType.CHERRY), + + CRIMSON_FUNGUS(TreeType.CRIMSON_FUNGUS), + WARPED_FUNGUS(TreeType.WARPED_FUNGUS), + + CHORUS_PLANT(TreeType.CHORUS_PLANT), + ; + + private final TreeType[] types; + + TreeSpecies(TreeType... types) { + this.types = types; + } + + /** + * Grow a tree at a location. + *

+ * If the species is a group, a random tree type is selected. + *

+ * + * @param location Location to grow the tree at + */ + public void grow(Location location) { + TreeType tree = CollectionUtils.getRandom(types); + assert tree != null; // No enum member causes empty types + World world = location.getWorld(); + if (world == null) { + return; + } + world.generateTree(location, tree); + } + + /** + * Grow a tree at a location. + *

+ * If the species is a group, a random tree type is selected. + *

+ * + * @param block Block to grow the tree at + */ + public void grow(Block block) { + grow(block.getLocation()); + } + + /** + * Get the TreeTypes that make up this species. + * + * @return TreeTypes that make up this species + */ + public TreeType[] getTypes() { + return types; + } + + /** + * Check if this species contains a specific tree type. + * + * @param type TreeType to check for + * @return True if the species contains the type, false otherwise + */ + public boolean is(TreeType type) { + return CollectionUtils.contains(types, type); + } + +} diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index a8cb8c8a4..f5ef3bc65 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -1,7 +1,6 @@ package ch.njol.skript.util; import ch.njol.skript.Skript; -import ch.njol.skript.effects.EffTeleport; import ch.njol.skript.localization.Language; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.region.TaskUtils; @@ -416,64 +415,6 @@ public static String a(final String s, final boolean capA) { } } - /** - * Gets the collision height of solid or partially-solid blocks at the center of the block. - * This is mostly for use in the {@link EffTeleport teleport effect}. - *

- * This version operates on numeric ids, thus only working on - * Minecraft 1.12 or older. - * - * @param type - * @return The block's height at the center - */ - public static double getBlockHeight(final int type, final byte data) { - switch (type) { - case 26: // bed - return 9. / 16; - case 44: // slabs - case 126: - return (data & 0x8) == 0 ? 0.5 : 1; - case 78: // snow layer - return data == 0 ? 1 : (data % 8) * 2. / 16; - case 85: // fences & gates - case 107: - case 113: - case 139: // cobblestone wall - return 1.5; - case 88: // soul sand - return 14. / 16; - case 92: // cake - return 7. / 16; - case 93: // redstone repeater - case 94: - case 149: // redstone comparator - case 150: - return 2. / 16; - case 96: // trapdoor - return (data & 0x4) == 0 ? ((data & 0x8) == 0 ? 3. / 16 : 1) : 0; - case 116: // enchantment table - return 12. / 16; - case 117: // brewing stand - return 14. / 16; - case 118: // cauldron - return 5. / 16; - case 120: // end portal frame - return (data & 0x4) == 0 ? 13. / 16 : 1; - case 127: // cocoa plant - return 12. / 16; - case 140: // flower pot - return 6. / 16; - case 144: // mob head - return 0.5; - case 151: // daylight sensor - return 6. / 16; - case 154: // hopper - return 10. / 16; - default: - return 1; - } - } - /** * Sends a plugin message using the first player from {@link Bukkit#getOnlinePlayers()}. *

diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index 6029be120..82091e4cc 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -1,11 +1,8 @@ package ch.njol.util; import ch.njol.skript.Skript; -import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; import org.jetbrains.annotations.ApiStatus; -import java.util.Arrays; - /** * This class is not to be used by addons. In the future methods may * change signature, contract and/or get removed without warning. @@ -152,6 +149,29 @@ public static long addClamped(long x, long y) { return result; } + /** + * @param x the first value + * @param y the second value + * @return the sum of x and y, or one of {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} in case of an overflow, + * whichever is closer to the real result + * */ + public static long addSaturated(long x, long y) { + long result = x + y; + boolean causedOverflow = ((x ^ result) & (y ^ result)) < 0; + if (causedOverflow) + return x < 0 && y < 0 ? Long.MIN_VALUE : Long.MAX_VALUE; + return result; + } + + /** + * Clamps a long to fit into the representable range for integers + * @return the value if it is between {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}, otherwise + * whichever of the two is closest to the value + * */ + public static int clampToInt(long value) { + return Math.clamp(value, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + public static long multiplyClamped(long x, long y) { long result = x * y; long ax = Math.abs(x); diff --git a/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java b/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java index b1e42eadc..ffa8d4bab 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java @@ -6,9 +6,11 @@ import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.bukkit.block.BlockModule; +import org.skriptlang.skript.bukkit.bossbar.BossBarModule; import org.skriptlang.skript.bukkit.breeding.BreedingModule; import org.skriptlang.skript.bukkit.brewing.BrewingModule; import org.skriptlang.skript.bukkit.damagesource.DamageSourceModule; +import org.skriptlang.skript.bukkit.enchantments.EnchantmentModule; import org.skriptlang.skript.bukkit.entity.EntityModule; import org.skriptlang.skript.bukkit.fishing.FishingModule; import org.skriptlang.skript.bukkit.input.InputModule; @@ -22,6 +24,7 @@ import org.skriptlang.skript.bukkit.tags.TagModule; import org.skriptlang.skript.bukkit.text.TextModule; import org.skriptlang.skript.bukkit.types.*; +import org.skriptlang.skript.bukkit.worldborder.elements.WorldBorderModule; import java.util.List; @@ -36,9 +39,11 @@ protected boolean canLoadSelf(SkriptAddon addon) { public Iterable children() { return List.of( new BlockModule(this), + new BossBarModule(this), new BreedingModule(this), new BrewingModule(this), new DamageSourceModule(this), + new EnchantmentModule(this), new EntityModule(this), new FishingModule(this), new InputModule(this), @@ -50,7 +55,8 @@ public Iterable children() { new PDCModule(this), new PotionModule(this), new TagModule(this), - new TextModule(this) + new TextModule(this), + new WorldBorderModule(this) ); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/furnace/elements/expressions/ExprFurnaceTime.java b/src/main/java/org/skriptlang/skript/bukkit/block/furnace/elements/expressions/ExprFurnaceTime.java index 72adf0fd3..3b060173b 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/furnace/elements/expressions/ExprFurnaceTime.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/furnace/elements/expressions/ExprFurnaceTime.java @@ -8,6 +8,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; import ch.njol.util.Kleenean; import ch.njol.util.Math2; import ch.njol.util.coll.CollectionUtils; @@ -109,20 +110,20 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return null; switch (type) { case COOKTIME -> { - return new Timespan(Timespan.TimePeriod.TICK, (int) furnace.getCookTime()); + return new Timespan(TimePeriod.TICK, (int) furnace.getCookTime()); } case TOTALCOOKTIME -> { if (event instanceof FurnaceStartSmeltEvent startEvent && block.equals(startEvent.getBlock())) { - return new Timespan(Timespan.TimePeriod.TICK, startEvent.getTotalCookTime()); + return new Timespan(TimePeriod.TICK, startEvent.getTotalCookTime()); } else { - return new Timespan(Timespan.TimePeriod.TICK, furnace.getCookTimeTotal()); + return new Timespan(TimePeriod.TICK, furnace.getCookTimeTotal()); } } case BURNTIME -> { if (event instanceof FurnaceBurnEvent burnEvent && block.equals(burnEvent.getBlock())) { - return new Timespan(Timespan.TimePeriod.TICK, burnEvent.getBurnTime()); + return new Timespan(TimePeriod.TICK, burnEvent.getBurnTime()); } else { - return new Timespan(Timespan.TimePeriod.TICK, (int) furnace.getBurnTime()); + return new Timespan(TimePeriod.TICK, (int) furnace.getBurnTime()); } } } @@ -142,7 +143,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { int providedTime = 0; if (delta != null && delta[0] instanceof Timespan span) - providedTime = (int) span.get(Timespan.TimePeriod.TICK); + providedTime = (int) span.getAs(TimePeriod.TICK); int finalTime = providedTime; Function calculateTime = switch (mode) { @@ -186,9 +187,10 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { */ private void changeFurnaces(Event event, Consumer changer) { for (Block block : getExpr().getArray(event)) { - Furnace furnace = (Furnace) block.getState(); - changer.accept(furnace); - furnace.update(true); + if (block != null && block.getState() instanceof Furnace furnace) { + changer.accept(furnace); + furnace.update(true); + } } } diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarClassInfo.java new file mode 100644 index 000000000..6f6a24a23 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarClassInfo.java @@ -0,0 +1,250 @@ +package org.skriptlang.skript.bukkit.bossbar; + +import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.util.coll.CollectionUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.boss.*; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; + +import ch.njol.skript.classes.Changer.ChangeMode; + +public class BossBarClassInfo extends ClassInfo { + + public BossBarClassInfo(SkriptAddon addon) { + super(BossBar.class, "bossbar"); + this.user("boss ?bars?") + .name("BossBar") + .description(""" + Represents a boss bar. + A boss bar is displayed at the top of a players screen and can have its title, progress, style, color, and viewers changed. + """) + .since("2.16") + .parser(new BossBarParser()) + .changer(new BossBarChangeHandler()) + .defaultExpression(new EventValueExpression<>(BossBar.class)) + .property(Property.TITLE, + """ + The title of a boss bar. + Note that due to Bukkit limitations formatting like shadow, sprites or fonts will not work. + """, + addon, + new BossBarTitleHandler()) + .property(Property.NAME, + """ + The name of a boss bar. + Note that due to Bukkit limitations formatting like shadow, sprites or fonts will not work. + """, + addon, + new BossBarTitleHandler()) + .property(Property.PROGRESS, + "The progress of a boss bar. Ranges from 0 to 1.", + addon, + new BossBarProgressHandler()) + .property(Property.STYLE, + "The style of a boss bar.", + addon, + new BossBarStyleHandler()) + .property(Property.VIEWERS, """ + The viewers of a boss bar. + If you remove a player from viewers of a boss bar they will no longer see the bossbar or be affected by its flags. + """, + addon, + new BossBarViewersHandler()); + } + + private static class BossBarParser extends Parser { + // + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(BossBar bar, int flags) { + boolean emptyTitle = bar.getTitle().isEmpty(); + if (emptyTitle) { + return "boss bar"; + } else { + return "boss bar titled '" + bar.getTitle() + "'"; + } + } + + @Override + public String toVariableNameString(BossBar bar) { + return toString(bar, 0); + } + // + } + + private static class BossBarChangeHandler implements Changer { + // + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.DELETE) + return CollectionUtils.array(); + return null; + } + + @Override + public void change(BossBar[] bars, Object @Nullable [] delta, ChangeMode mode) { + for (BossBar bar : bars) { + bar.removeAll(); + if (bar instanceof KeyedBossBar keyed) + Bukkit.removeBossBar(keyed.getKey()); + } + } + // + } + + private static class BossBarTitleHandler implements ExpressionPropertyHandler { + // + @Override + public @Nullable Component convert(BossBar bar) { + return LegacyComponentSerializer.legacySection().deserialize(bar.getTitle()); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(Component.class); + default -> null; + }; + } + + @Override + public void change(BossBar bar, Object @Nullable [] delta, ChangeMode mode) { + String title = delta == null ? null : + LegacyComponentSerializer.legacySection().serialize((Component) delta[0]); + bar.setTitle(title); + } + + @Override + public @NotNull Class returnType() { + return Component.class; + } + // + } + + private static class BossBarProgressHandler implements ExpressionPropertyHandler { + // + @Override + public @Nullable Double convert(BossBar bar) { + return bar.getProgress(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, RESET, ADD, REMOVE -> CollectionUtils.array(Double.class); + default -> null; + }; + } + + @Override + public void change(BossBar bar, Object @Nullable [] delta, ChangeMode mode) { + double progress = delta == null ? 0.0 : (double) delta[0]; + if (mode == ChangeMode.ADD) { + progress += bar.getProgress(); + } else if (mode == ChangeMode.REMOVE) { + progress -= bar.getProgress(); + } + bar.setProgress(Math.clamp(progress, 0.0, 1.0)); + } + + @Override + public @NotNull Class returnType() { + return Double.class; + } + // + } + + private static class BossBarStyleHandler implements ExpressionPropertyHandler { + // + @Override + public @Nullable BarStyle convert(BossBar bar) { + return bar.getStyle(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, RESET -> CollectionUtils.array(BarStyle.class); + default -> null; + }; + } + + @Override + public void change(BossBar bar, Object @Nullable [] delta, ChangeMode mode) { + BarStyle style = delta == null ? BarStyle.SOLID : (BarStyle) delta[0]; + bar.setStyle(style); + } + + @Override + public @NotNull Class returnType() { + return BarStyle.class; + } + // + } + + private static class BossBarViewersHandler implements ExpressionPropertyHandler { + // + @Override + public Player[] convert(BossBar bar) { + return bar.getPlayers().toArray(Player[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, DELETE, RESET -> CollectionUtils.array(Player[].class); + default -> null; + }; + } + + @Override + public void change(BossBar bar, Object @Nullable [] delta, ChangeMode mode) { + switch (mode) { + case SET -> { + bar.removeAll(); + assert delta != null; + for (Object player : delta) { + bar.addPlayer((Player) player); + } + } + case ADD -> { + assert delta != null; + for (Object player : delta) { + bar.addPlayer((Player) player); + } + } + case REMOVE -> { + assert delta != null; + for (Object player : delta) { + bar.removePlayer((Player) player); + } + } + case DELETE, RESET -> bar.removeAll(); + } + } + + @Override + public @NotNull Class returnType() { + //noinspection rawtypes, unchecked + return (Class) (Class) Player.class; + } + // + } + +} + diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarModule.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarModule.java new file mode 100644 index 000000000..0d2709821 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarModule.java @@ -0,0 +1,49 @@ +package org.skriptlang.skript.bukkit.bossbar; + +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.registrations.Classes; +import org.bukkit.boss.BarStyle; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.HierarchicalAddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.bossbar.elements.conditions.CondHasBossBarFlag; +import org.skriptlang.skript.bukkit.bossbar.elements.effects.EffBossBarFlags; +import org.skriptlang.skript.bukkit.bossbar.elements.expressions.*; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; + +public class BossBarModule extends HierarchicalAddonModule { + + public BossBarModule(AddonModule parentModule) { + super(parentModule); + } + + @Override + protected void initSelf(SkriptAddon addon) { + Classes.registerClass(new BossBarClassInfo(addon)); + Classes.registerClass(new KeyedBossBarClassInfo()); + Classes.registerClass(new EnumClassInfo<>(BarStyle.class, "bossbarstyle", "boss bar styles") + .user("boss ?bar styles?") + .name("Boss Bar Style") + .description("The style of a boss bar.") + .since("2.16")); + } + + @Override + protected void loadSelf(SkriptAddon addon) { + EventValueRegistry eventValueRegistry = addon.registry(EventValueRegistry.class); + register(addon, + syntaxRegistry -> ExprSecCreateBossBar.register(syntaxRegistry, eventValueRegistry), + ExprBossBarFromKey::register, + ExprBossBarFromEntity::register, + ExprKeyOfBossBar::register, + EffBossBarFlags::register, + CondHasBossBarFlag::register + ); + } + + @Override + public String name() { + return "boss bar"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarUtils.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarUtils.java new file mode 100644 index 000000000..ec24200d2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/BossBarUtils.java @@ -0,0 +1,70 @@ +package org.skriptlang.skript.bukkit.bossbar; + +import ch.njol.skript.util.Color; +import ch.njol.skript.util.SkriptColor; +import org.bukkit.boss.BarColor; +import org.skriptlang.skript.util.LabColor; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Comparator; + +/** + * A utility class for boss bars to help with getting RGB from SkriptColor and rounding an RGB to the nearest BarColor. + */ +public final class BossBarUtils { + + private BossBarUtils() {} + + /** + * All the skript colors for each bar color + */ + enum BarColorRGB { + + PINK(BarColor.PINK, SkriptColor.LIGHT_RED), + BLUE(BarColor.BLUE, SkriptColor.DARK_BLUE), + RED(BarColor.RED, SkriptColor.DARK_RED), + GREEN(BarColor.GREEN, SkriptColor.LIGHT_GREEN), + YELLOW(BarColor.YELLOW, SkriptColor.YELLOW), + PURPLE(BarColor.PURPLE, SkriptColor.DARK_PURPLE), + WHITE(BarColor.WHITE, SkriptColor.WHITE); + + final BarColor barColor; + final Color color; + + BarColorRGB(BarColor barColor, SkriptColor color) { + this.barColor = barColor; + this.color = color; + } + } + + /** + * Gets an RGB from a bar color cause well bukkit doesn't have it (& reasonably so) + * @param color the bar color to get the RGB color for + * @return the corresponding RGB color, or null + */ + public static Color rgbFromBarColor(BarColor color) { + return Arrays.stream(BarColorRGB.values()) + .filter(barColor -> barColor.barColor == color) + .map(barColor -> barColor.color) + .findFirst() + .orElse(null); + } + + /** + * Gets the nearest bossbar color from a skript color + * @param color the RGB color to find the nearest bar color for + * @return the nearest bar color, or null if no close enough match is found + */ + public static @Nullable BarColor nearest(Color color) { + LabColor labColor = LabColor.fromRGB(color.getRed(), color.getGreen(), color.getBlue()); + var value = Arrays.stream(BarColorRGB.values()) + .min(Comparator.comparingDouble(barColor -> labColor.euclideanDistanceSquared( + LabColor.fromRGB(barColor.color.getRed(), barColor.color.getGreen(), barColor.color.getBlue())))); + if (value.isEmpty()) + return null; + LabColor bestLab = LabColor.fromRGB(value.get().color.getRed(), value.get().color.getGreen(), value.get().color.getBlue()); + return labColor.euclideanDistanceSquared(bestLab) < 2500 ? value.get().barColor : null; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/KeyedBossBarClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/KeyedBossBarClassInfo.java new file mode 100644 index 000000000..4720b2473 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/KeyedBossBarClassInfo.java @@ -0,0 +1,116 @@ +package org.skriptlang.skript.bukkit.bossbar; + +import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.util.coll.CollectionUtils; +import ch.njol.yggdrasil.Fields; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.boss.KeyedBossBar; +import org.jetbrains.annotations.Nullable; + +import java.io.StreamCorruptedException; + +public class KeyedBossBarClassInfo extends ClassInfo { + + public KeyedBossBarClassInfo() { + super(KeyedBossBar.class, "keyedbossbar"); + this.user("keyed boss ?bars?") + .name(ClassInfo.NO_DOC) + .since("2.16") + .parser(new KeyedBossBarParser()) + .changer(new KeyedBossBarChangeHandler()) + .supplier(Bukkit::getBossBars) + .serializer(new KeyedBossBarSerializer()) + .defaultExpression(new EventValueExpression<>(KeyedBossBar.class)); + + } + + private static class KeyedBossBarParser extends Parser { + // + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(KeyedBossBar bar, int flags) { + boolean emptyTitle = bar.getTitle().isEmpty(); + if (emptyTitle) { + return "boss bar with id '" + bar.getKey() + "'"; + } else { + return "boss bar with id '" + bar.getKey() + "' titled '" + bar.getTitle() + "'"; + } + } + + @Override + public String toVariableNameString(KeyedBossBar bar) { + return toString(bar, 0); + } + // + } + + private static class KeyedBossBarChangeHandler implements Changer { + // + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.DELETE) + return CollectionUtils.array(); + return null; + } + + @Override + public void change(KeyedBossBar[] bars, Object @Nullable [] delta, ChangeMode mode) { + for (KeyedBossBar bar : bars) { + bar.removeAll(); + Bukkit.removeBossBar(bar.getKey()); + } + } + // + } + + private static class KeyedBossBarSerializer extends Serializer { + // + @Override + public Fields serialize(KeyedBossBar bar) { + Fields fields = new Fields(); + fields.putObject("key", bar.getKey().toString()); + return fields; + } + + @Override + public void deserialize(KeyedBossBar bar, Fields fields) { + assert false; + } + + @Override + protected KeyedBossBar deserialize(Fields fields) throws StreamCorruptedException { + String stringKey = fields.getObject("key", String.class); + NamespacedKey key = null; + if (stringKey != null) + key = NamespacedKey.fromString(stringKey); + if (key == null) + throw new StreamCorruptedException(); + KeyedBossBar bar = Bukkit.getBossBar(key); + if (bar == null) + throw new StreamCorruptedException(); + return bar; + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + public boolean canBeInstantiated() { + return false; + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/conditions/CondHasBossBarFlag.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/conditions/CondHasBossBarFlag.java new file mode 100644 index 000000000..622f2a9eb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/conditions/CondHasBossBarFlag.java @@ -0,0 +1,81 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.conditions; + +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.util.Patterns; +import ch.njol.util.Kleenean; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BossBar; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Has Boss Bar Flag") +@Description(""" + Checks whether a boss bar has a specific flag. + There are three flags: + - 'darken the sky' + - 'create fog' + - 'play boss music' + """) +@Example(""" + if {_mybar} does darken the sky: + broadcast "It's getting dark around here.." + """) +@Since("2.16") +public class CondHasBossBarFlag extends Condition { + + private static final Patterns PATTERNS = new Patterns<>(new Object[][]{ + {"%bossbars% [do[es]] darken[s] the sky", BarFlag.DARKEN_SKY}, + {"%bossbars% (doesn't|does not|do not|don't) darken the sky", BarFlag.DARKEN_SKY}, + {"%bossbars% [do[es]] create[s] fog", BarFlag.CREATE_FOG}, + {"%bossbars% (doesn't|does not|do not|don't) create fog", BarFlag.CREATE_FOG}, + {"%bossbars% [do[es]] play[s] boss music", BarFlag.PLAY_BOSS_MUSIC}, + {"%bossbars% (doesn't|does not|do not|don't) play boss music", BarFlag.PLAY_BOSS_MUSIC}, + }); + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.CONDITION, + SyntaxInfo.builder(CondHasBossBarFlag.class) + .addPatterns(PATTERNS.getPatterns()) + .supplier(CondHasBossBarFlag::new) + .build() + ); + } + + private Expression bars; + private BarFlag flag; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + flag = PATTERNS.getInfo(matchedPattern); + bars = (Expression) exprs[0]; + setNegated(matchedPattern % 2 != 0); + return true; + } + + @Override + public boolean check(Event event) { + return bars.check(event, bar -> bar.hasFlag(flag), isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append(bars) + .append(isNegated() ? "does not" : "does") + .append(switch (flag) { + case DARKEN_SKY -> "darken the sky"; + case CREATE_FOG -> "create fog"; + case PLAY_BOSS_MUSIC -> "play boss music"; + }) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/effects/EffBossBarFlags.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/effects/EffBossBarFlags.java new file mode 100644 index 000000000..b3b2da839 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/effects/EffBossBarFlags.java @@ -0,0 +1,92 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.effects; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +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.skript.util.Patterns; +import ch.njol.util.Kleenean; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BossBar; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@SuppressWarnings("unchecked") +@Name("Boss Bar Flags") +@Description(""" + Makes a boss bar have or not have a flag. + The `play boss music` flag does not actually play any sound or do anything. + However you can use a resource pack to play your own custom sound. + """) +@Example(""" + broadcast "The boss has entered a new phase! Watch out for the fog..." + make {_bossbar} create fog + wait 30 seconds + broadcast "The fog has subsided... for now" + make {_bossbar} no longer create fog + """) +@Since("2.16") +public class EffBossBarFlags extends Effect { + + private static final Patterns PATTERNS = new Patterns<>(new Object[][]{ + {"make %bossbars% [remove:(not|no longer)] darken the sky", BarFlag.DARKEN_SKY}, + {"stop %bossbar% from darkening the sky", BarFlag.DARKEN_SKY}, + {"make %bossbars% [remove:(not|no longer)] create fog", BarFlag.CREATE_FOG}, + {"stop %bossbar% from creating fog", BarFlag.CREATE_FOG}, + {"make %bossbars% [remove:(not|no longer)] play boss music", BarFlag.PLAY_BOSS_MUSIC}, + {"stop %bossbar% from playing [the] boss music", BarFlag.PLAY_BOSS_MUSIC}, + }); + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EFFECT, + SyntaxInfo.builder(EffBossBarFlags.class) + .addPatterns(PATTERNS.getPatterns()) + .supplier(EffBossBarFlags::new) + .build() + ); + } + + private Expression bars; + private BarFlag flag; + private boolean remove; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + bars = (Expression) expressions[0]; + remove = parseResult.hasTag("remove") || (matchedPattern % 2 == 1); + flag = PATTERNS.getInfo(matchedPattern); + return true; + } + + @Override + protected void execute(Event event) { + for (BossBar bar : bars.getArray(event)) { + if (remove) { + bar.removeFlag(flag); + } else { + bar.addFlag(flag); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("make", bars) + .appendIf(remove, "no longer") + .append(switch (flag) { + case BarFlag.DARKEN_SKY -> "darken the sky"; + case BarFlag.CREATE_FOG -> "create fog"; + case BarFlag.PLAY_BOSS_MUSIC -> "play boss music"; + }) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromEntity.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromEntity.java new file mode 100644 index 000000000..00f165de4 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromEntity.java @@ -0,0 +1,59 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.Boss; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Wither; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Boss Bar From Boss") +@Description(""" + Returns a boss bar from one or more bosses. + """) +@Example(""" + on spawn of wither: + set title of boss bar of event-entity to "Angry Wither" + """) +@Since("2.16") +public class ExprBossBarFromEntity extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprBossBarFromEntity.class, + BossBar.class, + "boss[ ]bar", + "entities", + false + ) + .supplier(ExprBossBarFromEntity::new) + .build() + ); + } + + @Override + public @Nullable BossBar convert(Entity entity) { + if (entity instanceof Boss boss) + return boss.getBossBar(); + return null; + } + + @Override + public Class getReturnType() { + return BossBar.class; + } + + @Override + protected String getPropertyName() { + return "boss bar"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromKey.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromKey.java new file mode 100644 index 000000000..96989d57a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprBossBarFromKey.java @@ -0,0 +1,89 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.expressions; + +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.SyntaxStringBuilder; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.boss.KeyedBossBar; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Boss Bar From Key") +@Description("Obtains a keyed boss bar from the specified key.") +@Example(""" + set {_bar} to a keyed red boss bar with key "example": + set title of event-bossbar to "My Test Title" + set color of event-bossbar to red + set progress of event-bossbar to 86% + set style of event-bossbar to 6 notches + + // some structure later on and you need to access it + set the title of the boss bar with the key "example" to "My Updated Test Title" + """) +@Since("2.16") +public class ExprBossBarFromKey extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprBossBarFromKey.class, KeyedBossBar.class) + .addPatterns("[the] boss[ ]bar[s] (from|with) [the] (id|key)[s] %strings%") + .supplier(ExprBossBarFromKey::new) + .build() + ); + } + + private Expression keys; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + keys = (Expression) exprs[0]; + return true; + } + + @Override + protected KeyedBossBar @Nullable [] get(Event event) { + List bars = new ArrayList<>(); + for (String string : keys.getArray(event)) { + NamespacedKey key = NamespacedUtils.checkValidationAndSend(string, this); + if (key == null) + continue; + KeyedBossBar bar = Bukkit.getBossBar(key); + if (bar != null) + bars.add(bar); + } + return bars.toArray(KeyedBossBar[]::new); + } + + @Override + public boolean isSingle() { + return keys.isSingle(); + } + + @Override + public Class getReturnType() { + return KeyedBossBar.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("boss bar from keys", keys) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprKeyOfBossBar.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprKeyOfBossBar.java new file mode 100644 index 000000000..26aefa367 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprKeyOfBossBar.java @@ -0,0 +1,58 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.boss.BossBar; +import org.bukkit.boss.KeyedBossBar; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + + +@Name("Key Of Boss Bar") +@Description(""" + Returns the key of a keyed boss bar. + Does not return anything for normal boss bars. + """) +@Example(""" + broadcast the boss bar key of {_mybar} + """) +@Since("2.16") +public class ExprKeyOfBossBar extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprKeyOfBossBar.class, + String.class, + "boss[ ]bar (key|id)", + "bossbars", + false + ) + .supplier(ExprKeyOfBossBar::new) + .build() + ); + } + + @Override + public @Nullable String convert(BossBar bar) { + if (bar instanceof KeyedBossBar keyed) { + return keyed.getKey().toString(); + } + return null; + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + protected String getPropertyName() { + return "boss bar key"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprSecCreateBossBar.java b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprSecCreateBossBar.java new file mode 100644 index 000000000..e2a58fba6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/bossbar/elements/expressions/ExprSecCreateBossBar.java @@ -0,0 +1,191 @@ +package org.skriptlang.skript.bukkit.bossbar.elements.expressions; + +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.util.Color; +import ch.njol.skript.variables.Variables; +import ch.njol.skript.doc.Example; +import ch.njol.util.Kleenean; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.boss.KeyedBossBar; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +import static org.skriptlang.skript.bukkit.bossbar.BossBarUtils.nearest; + +@Name("Create Boss Bar") +@Description(""" + Creates a new boss bar. + Boss bars can be pink/blue/red/green/yellow/purple/white, and will pick the closest valid color to the one you provide. + Boss bars can have viewers removed or added to them. + Making the boss bar 'keyed' will add it to the persistent storage of the server and will be editable by commands and restored after restart. + """) +@Example(""" + on join: + set {_bar} to a boss bar: + set color of event-bossbar to white + set title of event-bossbar to "Welcome %player%!" + set progress of event-bossbar to 50% + set style of event-bossbar to 6 notches + make event-bossbar darken the sky + add player to viewers of {_bar} + wait 5 seconds + remove player from viewers of {_bar} + """) +@Example(""" + command /createkeyedbar: + set {_bar} to a red keyed bossbar with title "My persistent red bar": + set progress of event-bossbar to 14% + set style of event-bossbar to 20 notches + loop all operators: + add loop-value to viewers of {_bar} + """) +@Since("2.16") +public class ExprSecCreateBossBar extends SectionExpression { + + public static void register(SyntaxRegistry syntaxRegistry, EventValueRegistry eventValueRegistry) { + syntaxRegistry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprSecCreateBossBar.class, BossBar.class) + .addPatterns("[a] [new] [%-color%] boss[ ]bar [(with title|titled) %-textcomponent%]", + "[a] [new] keyed [%-color%] boss[ ]bar with (id|key) %string% [(with title|titled) %-textcomponent%]") + .supplier(ExprSecCreateBossBar::new) + .build() + ); + + eventValueRegistry.register(EventValue.builder(CreateBossBarEvent.class, BossBar.class) + .getter(CreateBossBarEvent::getBossBar) + .build()); + } + + private Trigger trigger = null; + private Expression key; + private Expression color; + private Expression title; + private boolean isKeyed; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { + color = (Expression) expressions[0]; + if (matchedPattern == 1) { + isKeyed = true; + key = (Expression) expressions[1]; + } else { + isKeyed = false; + } + title = (Expression) expressions[expressions.length - 1]; + if (node != null) { + trigger = SectionUtils.loadLinkedCode("create bossbar", (beforeLoading, afterLoading) + -> loadCode(node, "create bossbar", beforeLoading, afterLoading, CreateBossBarEvent.class)); + return trigger != null; + } + return true; + } + + @Override + protected BossBar @Nullable [] get(Event event) { + BossBar bar; + Color color = null; + if (this.color != null) + color = this.color.getSingle(event); + BarColor barColor; + barColor = color != null && nearest(color) != null ? nearest(color) : BarColor.WHITE; + if (barColor == null) + return new BossBar[0]; + + String legacyTitle = null; + if (this.title != null) { + Component title = this.title.getSingle(event); + if (title == null) + return new BossBar[0]; + legacyTitle = LegacyComponentSerializer.legacySection().serialize(title); + } + + if (isKeyed) { + String stringKey = this.key.getSingle(event); + if (stringKey == null) + return new BossBar[0]; + NamespacedKey key = NamespacedUtils.checkValidationAndSend(stringKey, this); + if (key == null) + return new BossBar[0]; + Bukkit.createBossBar(key, legacyTitle, barColor, BarStyle.SOLID); + bar = Bukkit.getBossBar(key); + } else { + bar = Bukkit.createBossBar(legacyTitle, barColor, BarStyle.SOLID); + } + + if (trigger == null) + return new BossBar[] {bar}; + CreateBossBarEvent bossbarEvent = new CreateBossBarEvent(bar); + Variables.withLocalVariables(event, bossbarEvent, () -> TriggerItem.walk(trigger, bossbarEvent)); + return new BossBar[] {bossbarEvent.getBossBar()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + if (key != null) + return KeyedBossBar.class; + return BossBar.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("a") + .appendIf(key != null, "keyed") + .appendIf(color != null, color) + .append("boss bar") + .appendIf(key != null, "with key", key) + .appendIf(title != null, "with title", title) + .toString(); + } + + private static class CreateBossBarEvent extends Event { + private final BossBar bar; + + public CreateBossBarEvent(BossBar bar) { + this.bar = bar; + } + + public BossBar getBossBar() { + return bar; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new IllegalStateException(); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/EnchantmentModule.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/EnchantmentModule.java new file mode 100644 index 000000000..ab55563a0 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/EnchantmentModule.java @@ -0,0 +1,83 @@ +package org.skriptlang.skript.bukkit.enchantments; + +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.Experience; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentOffer; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.HierarchicalAddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.enchantments.elements.conditions.CondIsEnchanted; +import org.skriptlang.skript.bukkit.enchantments.elements.conditions.CondItemEnchantmentGlint; +import org.skriptlang.skript.bukkit.enchantments.elements.effects.EffEnchant; +import org.skriptlang.skript.bukkit.enchantments.elements.effects.EffForceEnchantmentGlint; +import org.skriptlang.skript.bukkit.enchantments.elements.expressions.*; +import org.skriptlang.skript.bukkit.enchantments.types.EnchantmentClassInfo; +import org.skriptlang.skript.bukkit.enchantments.types.EnchantmentOfferClassInfo; +import org.skriptlang.skript.bukkit.enchantments.types.EnchantmentTypeClassInfo; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; +import org.skriptlang.skript.lang.converter.Converters; + +public class EnchantmentModule extends HierarchicalAddonModule { + + public EnchantmentModule(AddonModule parentModule) { + super(parentModule); + } + + @Override + protected void initSelf(SkriptAddon addon) { + initClasses(); + initComparators(); + initConverters(); + } + + private void initClasses() { + Classes.registerClass(new EnchantmentClassInfo()); + Classes.registerClass(new EnchantmentTypeClassInfo()); + Classes.registerClass(new EnchantmentOfferClassInfo()); + } + + private void initComparators() { + Comparators.registerComparator(EnchantmentOffer.class, EnchantmentType.class, (offer, enchantmentType) -> + Relation.get(offer.getEnchantment() == enchantmentType.getType() && offer.getEnchantmentLevel() == enchantmentType.getLevel())); + Comparators.registerComparator(EnchantmentOffer.class, Experience.class, (offer, experience) -> + Relation.get(offer.getCost() == experience.getXP())); + Comparators.registerComparator(EnchantmentType.class, Enchantment.class, ((enchantmentType, enchantment) -> + Relation.get(enchantmentType.getType().equals(enchantment)))); + } + + private void initConverters() { + Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); + Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, eo -> new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel())); + } + + @Override + protected void loadSelf(SkriptAddon addon) { + register(addon, + CondIsEnchanted::register, + CondItemEnchantmentGlint::register, + EffEnchant::register, + EffForceEnchantmentGlint::register, + ExprAppliedEnchantments::register, + ExprEnchantingExpCost::register, + ExprEnchantItem::register, + ExprEnchantmentBonus::register, + ExprEnchantmentHint::register, + ExprEnchantmentLevel::register, + ExprEnchantmentOffer::register, + ExprEnchantmentOfferCost::register, + ExprEnchantments::register, + ExprItemWithEnchantmentGlint::register, + ExprMinMaxEnchantmentLevel::register, + ExprStoredEnchantments::register + ); + } + + @Override + public String name() { + return "enchantment"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsEnchanted.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondIsEnchanted.java similarity index 84% rename from src/main/java/ch/njol/skript/conditions/CondIsEnchanted.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondIsEnchanted.java index bc3fad378..0634797a8 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsEnchanted.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondIsEnchanted.java @@ -1,4 +1,4 @@ -package ch.njol.skript.conditions; +package org.skriptlang.skript.bukkit.enchantments.elements.conditions; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.conditions.base.PropertyCondition; @@ -14,6 +14,7 @@ import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Is Enchanted") @Description("Checks whether an item is enchanted. Enchants must match the exact level by default, unless 'or better' or 'or worse' are used.") @@ -39,8 +40,11 @@ public String toSkriptString() { } } - static { - PropertyCondition.register(CondIsEnchanted.class, "enchanted [with %-enchantmenttypes% [or (1:(better|greater|higher|above)|2:(worse|lesser|lower|below))]]", "itemtypes"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, PropertyCondition.infoBuilder(CondIsEnchanted.class, PropertyType.BE, + "enchanted [with %-enchantmenttypes% [or (1:(better|greater|higher|above)|2:(worse|lesser|lower|below))]]", "itemtypes") + .supplier(CondIsEnchanted::new) + .build()); } private Expression items; diff --git a/src/main/java/ch/njol/skript/conditions/CondItemEnchantmentGlint.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondItemEnchantmentGlint.java similarity index 53% rename from src/main/java/ch/njol/skript/conditions/CondItemEnchantmentGlint.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondItemEnchantmentGlint.java index aa93247cf..46dc0a35b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondItemEnchantmentGlint.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/conditions/CondItemEnchantmentGlint.java @@ -1,14 +1,14 @@ -package ch.njol.skript.conditions; +package org.skriptlang.skript.bukkit.enchantments.elements.conditions; import org.bukkit.inventory.meta.ItemMeta; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Item Has Enchantment Glint Override") @Description("Checks whether an item has the enchantment glint overridden, or is forced to glint or not.") @@ -24,44 +24,55 @@ else: send "This item does not have any glint override." to player """) -@RequiredPlugins("Spigot 1.20.5+") @Since("2.10") public class CondItemEnchantmentGlint extends PropertyCondition { - static { - if (Skript.methodExists(ItemMeta.class, "getEnchantmentGlintOverride")) { - register(CondItemEnchantmentGlint.class, PropertyType.HAVE, "enchantment glint overrid(den|e)", "itemtypes"); - register(CondItemEnchantmentGlint.class, PropertyType.BE, "forced to [:not] glint", "itemtypes"); - } + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, PropertyCondition + .infoBuilder(CondItemEnchantmentGlint.class, PropertyType.HAVE, "enchantment glint overrid(den|e)", "itemtypes") + .addPatterns(getPatterns(PropertyType.BE, "forced to [:not] glint", "itemtypes")) + .supplier(CondItemEnchantmentGlint::new) + .build()); } - private int matchedPattern; - private boolean glint; + private boolean expectedGlintOverride; + private boolean override; @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - this.matchedPattern = matchedPattern; - glint = !parseResult.hasTag("not"); - return super.init(expressions, matchedPattern, isDelayed, parseResult); + if (!super.init(expressions, matchedPattern, isDelayed, parseResult)) + return false; + override = matchedPattern <= 1; + expectedGlintOverride = !parseResult.hasTag("not"); + + // We override setNegated to correctly handle multiple patterns. Note that the [:not] parse tag is not negating. + setNegated(matchedPattern % 2 != 0); + return true; } @Override public boolean check(ItemType itemType) { ItemMeta meta = itemType.getItemMeta(); - // enchantment glint override - if (matchedPattern == 0) + + if (override) return meta.hasEnchantmentGlintOverride(); - // forced to glint + if (!meta.hasEnchantmentGlintOverride()) return false; - return meta.getEnchantmentGlintOverride(); + + return meta.getEnchantmentGlintOverride() == expectedGlintOverride; + } + + @Override + protected PropertyType getPropertyType() { + return override ? PropertyType.HAVE : PropertyType.BE; } @Override protected String getPropertyName() { - if (matchedPattern == 0) + if (override) return "enchantment glint overridden"; - return "forced to " + (glint ? "" : "not ") + "glint"; + return "forced to " + (expectedGlintOverride ? "" : "not ") + "glint"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffEnchant.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffEnchant.java similarity index 82% rename from src/main/java/ch/njol/skript/effects/EffEnchant.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffEnchant.java index f9021d646..6b9b1f0cc 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnchant.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffEnchant.java @@ -1,4 +1,4 @@ -package ch.njol.skript.effects; +package org.skriptlang.skript.bukkit.enchantments.elements.effects; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; @@ -16,6 +16,8 @@ import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; @@ -37,16 +39,18 @@ private enum Operation { DISENCHANT } - private static final Patterns patterns; + private static final Patterns PATTERNS = new Patterns<>(new Object[][]{ + {"enchant %~itemtypes% with %enchantmenttypes%", Operation.ENCHANT}, + {"[naturally|randomly] enchant %~itemtypes% at level %number%[treasure:[,] allowing treasure enchant[ment]s]", + Operation.ENCHANT_AT_LEVEL}, + {"disenchant %~itemtypes%", Operation.DISENCHANT} + }); - static { - patterns = new Patterns<>(new Object[][]{ - {"enchant %~itemtypes% with %enchantmenttypes%", Operation.ENCHANT}, - {"[naturally|randomly] enchant %~itemtypes% at level %number%[treasure:[,] allowing treasure enchant[ment]s]", - Operation.ENCHANT_AT_LEVEL}, - {"disenchant %~itemtypes%", Operation.DISENCHANT} - }); - Skript.registerEffect(EffEnchant.class, patterns.getPatterns()); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffEnchant.class) + .addPatterns(PATTERNS.getPatterns()) + .supplier(EffEnchant::new) + .build()); } private Expression items; @@ -69,7 +73,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye level = (Expression) exprs[1]; treasure = parseResult.hasTag("treasure"); } - operation = patterns.getInfo(matchedPattern); + operation = PATTERNS.getInfo(matchedPattern); return true; } diff --git a/src/main/java/ch/njol/skript/effects/EffForceEnchantmentGlint.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffForceEnchantmentGlint.java similarity index 64% rename from src/main/java/ch/njol/skript/effects/EffForceEnchantmentGlint.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffForceEnchantmentGlint.java index 956e43023..0545d5afc 100644 --- a/src/main/java/ch/njol/skript/effects/EffForceEnchantmentGlint.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/effects/EffForceEnchantmentGlint.java @@ -1,32 +1,34 @@ -package ch.njol.skript.effects; +package org.skriptlang.skript.bukkit.enchantments.elements.effects; import org.bukkit.event.Event; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Force Enchantment Glint") @Description("Forces the items to glint or not, or removes its existing enchantment glint enforcement.") @Example("force {_items::*} to glint") @Example("force the player's tool to stop glinting") -@RequiredPlugins("Spigot 1.20.5+") @Since("2.10") public class EffForceEnchantmentGlint extends Effect { - static { - if (Skript.methodExists(ItemMeta.class, "setEnchantmentGlintOverride", Boolean.class)) - Skript.registerEffect(EffForceEnchantmentGlint.class, - "(force|make) %itemtypes% [to] [start] glint[ing]", - "(force|make) %itemtypes% [to] (not|stop) glint[ing]", - "(clear|delete|reset) [the] enchantment glint override of %itemtypes%", - "(clear|delete|reset) %itemtypes%'s enchantment glint override"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffForceEnchantmentGlint.class) + .addPatterns( + "(force|make) %itemtypes% [to] [start] glint[ing]", + "(force|make) %itemtypes% [to] (not|stop) glint[ing]", + "(clear|delete|reset) [the] enchantment glint override of %itemtypes%", + "(clear|delete|reset) %itemtypes%'[s] enchantment glint override") + .supplier(EffForceEnchantmentGlint::new) + .build()); } private Expression itemTypes; @@ -44,17 +46,11 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is protected void execute(Event event) { for (ItemType itemType : itemTypes.getArray(event)) { ItemMeta meta = itemType.getItemMeta(); - Boolean glint; - if (pattern == 0) { - // Pattern: forced to glint - glint = true; - } else if (pattern == 1) { - // Pattern: forced to not glint - glint = false; - } else { - // Pattern: Clear glint override - glint = null; - } + Boolean glint = switch (pattern) { + case 0 -> true; // Pattern: forced to glint + case 1 -> false; // Pattern: forced to not glint + default -> null; // Pattern: Clear glint override + }; meta.setEnchantmentGlintOverride(glint); itemType.setItemMeta(meta); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprAppliedEnchantments.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprAppliedEnchantments.java new file mode 100644 index 000000000..b59738ed7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprAppliedEnchantments.java @@ -0,0 +1,120 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.lang.EventRestrictedSyntax; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.jetbrains.annotations.Nullable; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Applied Enchantments") +@Description(""" + The applied enchantments in an enchant event. + Deleting or removing the applied enchantments will prevent the item's enchantment. + """) +@Example(""" + on enchant: + set the applied enchantments to sharpness 10 and fire aspect 5 + """) +@Events("enchant") +@Since("2.5") +public class ExprAppliedEnchantments extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + builder(ExprAppliedEnchantments.class, EnchantmentType.class) + .addPattern("[the] applied enchant[ment]s") + .supplier(ExprAppliedEnchantments::new) + .build()); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(EnchantItemEvent.class); + } + + @Override + protected EnchantmentType @Nullable [] get(Event event) { + if (!(event instanceof EnchantItemEvent enchantEvent)) + return new EnchantmentType[0]; + + return enchantEvent.getEnchantsToAdd().entrySet().stream() + .map(entry -> new EnchantmentType(entry.getKey(), entry.getValue())) + .toArray(EnchantmentType[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, DELETE, REMOVE -> CollectionUtils.array(EnchantmentType[].class); + default -> null; + }; + } + + @SuppressWarnings("null") + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof EnchantItemEvent enchantEvent)) + return; + + EnchantmentType[] enchants = new EnchantmentType[delta != null ? delta.length : 0]; + if (delta != null && delta.length != 0) { + for (int i = 0; i < delta.length; i++) { + enchants[i] = (EnchantmentType) delta[i]; + } + } + + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + enchantEvent.getEnchantsToAdd().clear(); + + switch (mode) { + case DELETE -> {} + case SET, ADD -> { + for (EnchantmentType enchant : enchants) + enchantEvent.getEnchantsToAdd().put(enchant.getType(), enchant.getLevel()); + } + case REMOVE -> { + for (EnchantmentType enchant : enchants) + enchantEvent.getEnchantsToAdd().remove(enchant.getType(), enchant.getLevel()); + } + default -> { + assert false; + } + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return EnchantmentType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the applied enchantments"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantItem.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantItem.java new file mode 100644 index 000000000..737e46012 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantItem.java @@ -0,0 +1,127 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.lang.EventRestrictedSyntax; +import org.bukkit.Material; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.enchantment.PrepareItemEnchantEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.lang.script.ScriptWarning; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Enchant Item") +@Description(""" + The enchant item in an enchant prepare event or enchant event. + It can be modified, but enchantments will still be applied in the enchant event. + """) +@Example(""" + on enchant: + set the enchanted item to a diamond chestplate + """) +@Example(""" + on enchant prepare: + set the enchant item to a wooden sword + """) +@Events({"enchant prepare", "enchant"}) +@Since("2.5") +public class ExprEnchantItem extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantItem.class, ItemType.class) + .addPatterns("[the] enchant[:ed] item") + .supplier(ExprEnchantItem::new) + .build()); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!parseResult.hasTag("ed")) + ScriptWarning.printDeprecationWarning("The 'enchant item' form of the enchanted item expression is deprecated, please use 'enchanted item'!"); + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(EnchantItemEvent.class, PrepareItemEnchantEvent.class); + } + + @Override + protected ItemType[] get(Event event) { + return new ItemType[]{new ItemType(switch (event) { + case PrepareItemEnchantEvent prepare -> prepare.getItem(); + case EnchantItemEvent enchant -> enchant.getItem(); + case null, default -> throw new IllegalStateException("Unsupported event " + event); + })}; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(ItemType.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + ItemType item = ((ItemType) delta[0]); + + switch (event) { + case EnchantItemEvent enchant -> { + ItemStack created = item.getRandom(); + enchant.setItem(created != null ? created : ItemStack.empty()); + } + case PrepareItemEnchantEvent prepare -> { + ItemStack existing = prepare.getItem(); + Material target = existing.getType(); + boolean mayChangeType = false; + for (Material candidate : item.getMaterials()) + mayChangeType |= candidate != target; + + if (mayChangeType) { + warning("Changing the item type of the enchant item in a prepare item enchant event " + + "is deprecated for removal and will not be supported in the future."); + + //noinspection deprecation - user error + existing.setType(item.getMaterial()); + } + + existing.setItemMeta(item.getItemMeta()); + existing.setAmount(item.getAmount()); + } + default -> throw new AssertionError("unreachable"); + } + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ItemType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "enchanted item"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantingExpCost.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantingExpCost.java new file mode 100644 index 000000000..1296c19b1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantingExpCost.java @@ -0,0 +1,106 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.util.Math2; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.jetbrains.annotations.Nullable; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Enchanting Experience Cost") +@Description(""" + The cost of enchanting in an enchant event. + This is number that was displayed in the enchantment table, not the actual number of levels removed. + """) +@Example(""" + on enchant: + send "Cost: %the displayed enchanting cost%" to player + """) +@Events("enchant") +@Since("2.5") +public class ExprEnchantingExpCost extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantingExpCost.class, Integer.class) + .addPattern("[the] [displayed] ([e]xp[erience]|enchanting) cost") + .supplier(ExprEnchantingExpCost::new) + .build()); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(EnchantItemEvent.class); + } + + @Override + protected Integer @Nullable [] get(Event event) { + if (!(event instanceof EnchantItemEvent enchantEvent)) + return null; + + return new Integer[]{enchantEvent.getExpLevelCost()}; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE -> CollectionUtils.array(Number.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (delta == null || delta.length == 0 || !(event instanceof EnchantItemEvent enchantEvent)) + return; + long cost = ((Number) delta[0]).longValue(); + switch (mode) { + case SET -> enchantEvent.setExpLevelCost(Math.max(1, Math2.clampToInt(cost))); + case ADD -> { + long add = Math.max(1, Math2.addSaturated(enchantEvent.getExpLevelCost(), cost)); + enchantEvent.setExpLevelCost(Math2.clampToInt(add)); + } + case REMOVE -> { + long subtract = Math.max(1, Math2.addSaturated(enchantEvent.getExpLevelCost(), -cost)); + enchantEvent.setExpLevelCost(Math2.clampToInt(subtract)); + } + default -> { + assert false; + } + } + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the displayed enchanting cost"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentBonus.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentBonus.java new file mode 100644 index 000000000..30b58fd6a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentBonus.java @@ -0,0 +1,71 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.PrepareItemEnchantEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Enchantment Bonus") +@Description("The enchantment bonus in an enchant prepare event. This represents the number of bookshelves affecting/surrounding the enchantment table.") +@Example(""" + on enchant: + send "There are %enchantment bonus% bookshelves surrounding this enchantment table!" to player + """) +@Events("enchant prepare") +@Since("2.5") +public class ExprEnchantmentBonus extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantmentBonus.class, Integer.class) + .addPattern("[the] enchant[ment] bonus") + .supplier(ExprEnchantmentBonus::new) + .build()); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + @SuppressWarnings("unchecked") /* hard-coded event type */ + public Class[] supportedEvents() { + return new Class[]{PrepareItemEnchantEvent.class}; + } + + @Override + protected Integer @Nullable [] get(Event event) { + if (!(event instanceof PrepareItemEnchantEvent prepare)) + return null; + + return new Integer[]{prepare.getEnchantmentBonus()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "enchantment bonus"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentHint.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentHint.java new file mode 100644 index 000000000..aa9ee5545 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentHint.java @@ -0,0 +1,82 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Enchantment Hint") +@Description(""" + The enchantment hint in an enchant event. + This is the enchantment that was shown to the player when they hovered over the enchantment offer that they eventually selected. + """) +@Example(""" + on enchant: + send "You got at least %the enchantment hint%!" + """) +@Events("enchant") +@Since("2.16") +public class ExprEnchantmentHint extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantmentHint.class, Enchantment.class) + .addPatterns("[the] enchant[ment] hint") + .supplier(ExprEnchantmentHint::new) + .build()); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(EnchantItemEvent.class); + } + + @Override + protected Enchantment[] get(Event event) { + if (!(event instanceof EnchantItemEvent enchant)) + return new Enchantment[0]; + + return CollectionUtils.array(enchant.getEnchantmentHint()); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return null; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Enchantment.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the enchantment hint"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentLevel.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentLevel.java similarity index 52% rename from src/main/java/ch/njol/skript/expressions/ExprEnchantmentLevel.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentLevel.java index 9fa8ffd77..401c2a321 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentLevel.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentLevel.java @@ -1,25 +1,28 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.EnchantmentType; import ch.njol.util.Kleenean; +import ch.njol.util.Math2; import ch.njol.util.coll.CollectionUtils; import org.bukkit.enchantments.Enchantment; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.stream.Stream; +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + @Name("Enchantment Level") @Description("The level of a particular enchantment on an item.") @Example(""" @@ -29,18 +32,20 @@ @Since("2.0") public class ExprEnchantmentLevel extends SimpleExpression { - static { - Skript.registerExpression(ExprEnchantmentLevel.class, Long.class, ExpressionType.PROPERTY, - "[the] [enchant[ment]] level[s] of %enchantments% (on|of) %itemtypes%", - "[the] %enchantments% [enchant[ment]] level[s] (on|of) %itemtypes%", - "%itemtypes%'[s] %enchantments% [enchant[ment]] level[s]", - "%itemtypes%'[s] [enchant[ment]] level[s] of %enchantments%"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantmentLevel.class, Long.class) + .addPatterns( + "[the] [enchant[ment]] level[s] of %enchantments% (on|of) %itemtypes%", + "[the] %enchantments% [enchant[ment]] level[s] (on|of) %itemtypes%", + "%itemtypes%'[s] %enchantments% [enchant[ment]] level[s]", + "%itemtypes%'[s] [enchant[ment]] level[s] of %enchantments%") + .supplier(ExprEnchantmentLevel::new) + .priority(PropertyExpression.DEFAULT_PRIORITY) + .build()); } - @SuppressWarnings("NotNullFieldNotInitialized") private Expression items; - @SuppressWarnings("NotNullFieldNotInitialized") private Expression enchants; @Override @@ -53,9 +58,9 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected Long[] get(Event e) { - Enchantment[] enchantments = enchants.getArray(e); - return Stream.of(items.getArray(e)) + protected Long[] get(Event event) { + Enchantment[] enchantments = enchants.getArray(event); + return Stream.of(items.getArray(event)) .map(ItemType::getEnchantmentTypes) .flatMap(Stream::of) .filter(enchantment -> CollectionUtils.contains(enchantments, enchantment.getType())) @@ -65,49 +70,40 @@ protected Long[] get(Event e) { } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - switch (mode) { - case SET: - case REMOVE: - case ADD: - return CollectionUtils.array(Number.class); - default: - return null; - } + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, REMOVE, ADD -> CollectionUtils.array(Number.class); + default -> null; + }; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - ItemType[] itemTypes = items.getArray(e); - Enchantment[] enchantments = enchants.getArray(e); - int changeValue = ((Number) delta[0]).intValue(); + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + ItemType[] itemTypes = items.getArray(event); + Enchantment[] enchantments = enchants.getArray(event); + assert delta != null; + long changeValue = ((Number) delta[0]).longValue(); for (ItemType itemType : itemTypes) { for (Enchantment enchantment : enchantments) { EnchantmentType enchantmentType = itemType.getEnchantmentType(enchantment); - int oldLevel = enchantmentType == null ? 0 : enchantmentType.getLevel(); + long oldLevel = enchantmentType == null ? 0 : enchantmentType.getLevel(); - int newItemLevel; + long newItemLevel; switch (mode) { - case ADD: - newItemLevel = oldLevel + changeValue; - break; - case REMOVE: - newItemLevel = oldLevel - changeValue; - break; - case SET: - newItemLevel = changeValue; - break; - default: + case ADD -> newItemLevel = Math2.addSaturated(oldLevel, changeValue); + case REMOVE -> newItemLevel = Math2.addSaturated(oldLevel, -changeValue); + case SET -> newItemLevel = changeValue; + default -> { assert false; return; + } } if (newItemLevel <= 0) { itemType.removeEnchantments(new EnchantmentType(enchantment)); } else { - itemType.addEnchantments(new EnchantmentType(enchantment, newItemLevel)); + itemType.addEnchantments(new EnchantmentType(enchantment, Math2.clampToInt(newItemLevel))); } } } @@ -124,8 +120,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the level of " + enchants.toString(e, debug) + " of " + items.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the level of " + enchants.toString(event, debug) + " of " + items.toString(event, debug); } } diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOffer.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOffer.java new file mode 100644 index 000000000..62e465b57 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOffer.java @@ -0,0 +1,192 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import ch.njol.skript.lang.EventRestrictedSyntax; +import org.bukkit.enchantments.EnchantmentOffer; +import org.bukkit.event.Event; +import org.bukkit.event.enchantment.PrepareItemEnchantEvent; +import org.jetbrains.annotations.Nullable; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; + +@Name("Enchantment Offer") +@Description("The enchantment offer in enchant prepare events.") +@Example(""" + on enchant prepare: + send "Your enchantment offers are: %the enchantment offers%" to player + """) +@Since("2.5") +@Events("enchant prepare") +public class ExprEnchantmentOffer extends SimpleExpression implements EventRestrictedSyntax { + + /* + * This should probably be an event value, but ExprElement doesn't support the %integer%(st|nd|rd|th) %classinfo% syntax, + * and we have to keep it for backward compatibility, so for now it's best to just keep it as an expression + * */ + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprEnchantmentOffer.class, EnchantmentOffer.class) + .addPatterns( + "[all [[of] the]|the] enchant[ment] offers", + "enchant[ment] offer[s] %integers%", + "[the] %integer%(st|nd|rd|th) enchant[ment] offer") + .supplier(ExprEnchantmentOffer::new) + .priority(SyntaxInfo.SIMPLE) + .build()); + } + + private Expression offerNumber; + + private boolean all; + + // Used for getCost() + private final Random rand = new Random(); + + @Override + @SuppressWarnings({"null", "unchecked"}) + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0) { + all = true; + } else { + offerNumber = (Expression) exprs[0]; + all = false; + } + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(PrepareItemEnchantEvent.class); + } + + @Override + protected EnchantmentOffer @Nullable [] get(Event event) { + if (!(event instanceof PrepareItemEnchantEvent enchant)) + return null; + + if (all) + return enchant.getOffers(); + if (offerNumber == null) + return new EnchantmentOffer[0]; + if (offerNumber.isSingle()) { + Integer offer = offerNumber.getSingle(event); + if (offer == null) + return new EnchantmentOffer[0]; + if (offer < 1 || offer > enchant.getOffers().length) + return new EnchantmentOffer[0]; + return new EnchantmentOffer[]{enchant.getOffers()[offer - 1]}; + } + List offers = new ArrayList<>(); + for (Integer index : offerNumber.getArray(event)) { + if (index >= 1 && index <= enchant.getOffers().length) + offers.add(enchant.getOffers()[index - 1]); + } + return offers.toArray(new EnchantmentOffer[0]); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(EnchantmentType.class); + return null; + } + + @Override + @SuppressWarnings("null") + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null || mode == ChangeMode.DELETE; + EnchantmentType type = mode != ChangeMode.DELETE ? (EnchantmentType) delta[0] : null; + if (!(event instanceof PrepareItemEnchantEvent prepareEvent)) + return; + + switch (mode) { + case SET -> { + assert type != null; + Integer[] indices = all ? new Integer[]{1, 2, 3} : offerNumber.getArray(prepareEvent); + for (Integer index : indices) { + int slot = index - 1; + if (slot < 0 || slot >= prepareEvent.getOffers().length) + continue; + + EnchantmentOffer offer = prepareEvent.getOffers()[slot]; + if (offer == null) { + offer = new EnchantmentOffer(type.getType(), type.getLevel(), + getCost(slot + 1, prepareEvent.getEnchantmentBonus())); + prepareEvent.getOffers()[slot] = offer; + } else { + offer.setEnchantment(type.getType()); + offer.setEnchantmentLevel(type.getLevel()); + } + } + } + case DELETE -> { + if (all) { + Arrays.fill(prepareEvent.getOffers(), null); + } else { + for (Integer index : offerNumber.getArray(prepareEvent)) { + int slot = index - 1; + if (slot < 0 || slot >= prepareEvent.getOffers().length) + continue; + + prepareEvent.getOffers()[slot] = null; + } + } + } + default -> { + assert false; + } + } + } + + @Override + public boolean isSingle() { + return !all && offerNumber.isSingle(); + } + + @Override + public Class getReturnType() { + return EnchantmentOffer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return all ? "the enchantment offers" : "enchantment offer " + offerNumber.toString(event, debug); + } + + /** + * Returns an enchantment cost from an enchantment button and number of bookshelves. + * @param slot The enchantment button slot (1, 2, or 3). + * @param bookshelves The number of bookshelves around the enchantment table. + * @return A cost for that enchantment button with the number of bookshelves, or 1 if 'slot' is not an integer from 1 to 3. + */ + public int getCost(int slot, int bookshelves) { + // Based on https://minecraft.wiki/w/Enchanting_table_mechanics#Basic_mechanics + // (from 1 to 8) + floor(bookshelves / 2) + (from 0 to bookshelves) + int base = (rand.nextInt(8) + 1) + (bookshelves / 2) + (rand.nextInt(bookshelves + 1)); + return switch (slot) { + case 1 -> Math.max(base / 3, 1); + case 2 -> (base * 2) / 3 + 1; + case 3 -> Math.max(base, bookshelves * 2); + default -> 1; + }; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOfferCost.java similarity index 56% rename from src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOfferCost.java index 1aa1844b0..e2b838c34 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantmentOfferCost.java @@ -1,19 +1,18 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; +import ch.njol.util.Math2; import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.util.Experience; import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Enchantment Offer Cost") @Description({ @@ -24,12 +23,13 @@ }) @Example("set cost of enchantment offer 1 to 50") @Since("2.5") -@RequiredPlugins("1.11 or newer") public class ExprEnchantmentOfferCost extends SimplePropertyExpression { - static { - if (Skript.classExists("org.bukkit.enchantments.EnchantmentOffer")) - register(ExprEnchantmentOfferCost.class, Long.class, "[enchant[ment]] cost", "enchantmentoffers"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprEnchantmentOfferCost.class, Long.class, "[enchant[ment]] cost", "enchantmentoffers", false) + .supplier(ExprEnchantmentOfferCost::new) + .build()); } @Override @@ -38,48 +38,40 @@ public Long convert(final EnchantmentOffer offer) { } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { if (mode == ChangeMode.REMOVE || mode == ChangeMode.REMOVE_ALL || mode == ChangeMode.RESET) return null; - return CollectionUtils.array(Number.class, Experience.class); + return CollectionUtils.array(Number.class); } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { EnchantmentOffer[] offers = getExpr().getArray(event); if (offers.length == 0 || delta == null) return; - Object c = delta[0]; - int cost = c instanceof Number ? ((Number) c).intValue() : ((Experience) c).getXP(); + long cost = ((Number) delta[0]).longValue(); if (cost < 1) return; - int change; - switch (mode) { - case SET: - for (EnchantmentOffer offer : offers) - offer.setCost(cost); - break; - case ADD: - for (EnchantmentOffer offer : offers) { - change = offer.getCost() + cost; - if (change < 1) + long change; + for (EnchantmentOffer offer : offers) { + switch (mode) { + case SET -> offer.setCost(Math2.clampToInt(cost)); + case ADD -> { + change = Math2.addClamped(offer.getCost(), cost); + if (change < 1) return; - offer.setCost(change); + offer.setCost(Math2.clampToInt(change)); } - break; - case REMOVE: - for (EnchantmentOffer offer : offers) { - change = offer.getCost() - cost; - if (change < 1) + case REMOVE -> { + change = Math2.addClamped(offer.getCost(), -cost); + if (change < 1) return; - offer.setCost(change); + offer.setCost(Math2.clampToInt(change)); } - break; - case RESET: - case DELETE: - case REMOVE_ALL: - assert false; + default -> { + assert false; + } + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantments.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantments.java similarity index 64% rename from src/main/java/ch/njol/skript/expressions/ExprEnchantments.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantments.java index 2df748755..145ca86b9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantments.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprEnchantments.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; import java.util.ArrayList; import java.util.Collections; @@ -18,28 +18,28 @@ import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.EnchantmentType; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Item Enchantments") @Description("All the enchantments an item type has.") @Example("clear enchantments of event-item") @Since("2.2-dev36") -public class ExprEnchantments extends SimpleExpression { +public class ExprEnchantments extends PropertyExpression { - static { - PropertyExpression.register(ExprEnchantments.class, EnchantmentType.class, "enchantments", "itemtypes"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprEnchantments.class, EnchantmentType.class, "enchantments", "itemtypes", false) + .supplier(ExprEnchantments::new) + .build()); } - @SuppressWarnings("null") - private Expression items; - - @SuppressWarnings({"null","unchecked"}) @Override + @SuppressWarnings({"null","unchecked"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - items = (Expression) exprs[0]; + setExpr((Expression) exprs[0]); return true; } @@ -49,11 +49,9 @@ public boolean isSingle() { } @Override - @Nullable - protected EnchantmentType[] get(Event e) { + protected EnchantmentType[] get(Event event, ItemType[] source) { List enchantments = new ArrayList<>(); - - for (ItemType item : items.getArray(e)) { + for (ItemType item : source) { EnchantmentType[] enchants = item.getEnchantmentTypes(); if (enchants == null) @@ -65,27 +63,23 @@ protected EnchantmentType[] get(Event e) { } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { - // Enchantment doesn't get automatically converted to EnchantmentType if you give it more than a one. - // Meaning you can transform an Enchantment array to an EnchantmentType array automatically, - // So, we gotta do it manually. - return CollectionUtils.array(Enchantment[].class, EnchantmentType[].class); + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, REMOVE, REMOVE_ALL, DELETE, RESET -> CollectionUtils.array(EnchantmentType[].class); + default -> null; + }; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - ItemType[] source = items.getArray(e); - + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + ItemType[] source = getExpr().getArray(event); + EnchantmentType[] enchants = new EnchantmentType[delta != null ? delta.length : 0]; - - if (delta != null && delta.length != 0) { - for (int i = 0; i getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the enchantments of " + items.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the enchantments of " + getExpr().toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemWithEnchantmentGlint.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprItemWithEnchantmentGlint.java similarity index 73% rename from src/main/java/ch/njol/skript/expressions/ExprItemWithEnchantmentGlint.java rename to src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprItemWithEnchantmentGlint.java index e763798f4..4e389d99c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemWithEnchantmentGlint.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprItemWithEnchantmentGlint.java @@ -1,29 +1,32 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; import org.bukkit.event.Event; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.doc.*; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import static org.skriptlang.skript.registration.DefaultSyntaxInfos.Expression.builder; @Name("Item with Enchantment Glint") @Description("Get an item with or without enchantment glint.") @Example("set {_item with glint} to diamond with enchantment glint") @Example("set {_item without glint} to diamond without enchantment glint") -@RequiredPlugins("Spigot 1.20.5+") @Since("2.10") public class ExprItemWithEnchantmentGlint extends PropertyExpression { - static { - if (Skript.methodExists(ItemMeta.class, "getEnchantmentGlintOverride")) - Skript.registerExpression(ExprItemWithEnchantmentGlint.class, ItemType.class, ExpressionType.PROPERTY, "%itemtypes% with[:out] [enchant[ment]] glint"); + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, builder(ExprItemWithEnchantmentGlint.class, ItemType.class) + .addPattern("%itemtypes% with[:out] [enchant[ment]] glint") + .supplier(ExprItemWithEnchantmentGlint::new) + .priority(PropertyExpression.DEFAULT_PRIORITY) + .build()); } private boolean glint; diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprMinMaxEnchantmentLevel.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprMinMaxEnchantmentLevel.java new file mode 100644 index 000000000..122da0e11 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprMinMaxEnchantmentLevel.java @@ -0,0 +1,66 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Minimum/Maximum Enchantment Level") +@Description(""" + The minimum or maximum allowed level in Minecraft of a particular enchantment. + + The minimum starting level is 1 for all existing enchantments as of 26.1.2. + """) +@Example(""" + set {_maximum} to the maximum enchantment level of sharpness + if the level of sharpness of the player's tool is greater than {_maximum}: + send "Your tool's sharpness level was capped out at the maximum allowed level." + set the level of sharpness of the player's tool to {_maximum} + """) +@Example(""" + set {_min} to the minimum enchantment level of sharpness + set {_max} to the maximum enchantment level of sharpness + loop integers between {_min} and {_max}: + set slot loop-counter of {_gui} to enchanted book named "Sharpness %loop-value%" with lore "Click to enchant!" + """) +@Since("2.16") +public class ExprMinMaxEnchantmentLevel extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprMinMaxEnchantmentLevel.class, Integer.class, + "((:max|min)[imum]|starting) enchant[ment] level", "enchantments", true) + .supplier(ExprMinMaxEnchantmentLevel::new) + .build()); + } + + private boolean maximum = false; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + maximum = parseResult.hasTag("max"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable Integer convert(Enchantment from) { + return maximum ? from.getMaxLevel() : from.getStartLevel(); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return (maximum ? "maximum" : "minimum") + " enchantment level"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprStoredEnchantments.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprStoredEnchantments.java new file mode 100644 index 000000000..5630f6267 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/elements/expressions/ExprStoredEnchantments.java @@ -0,0 +1,146 @@ +package org.skriptlang.skript.bukkit.enchantments.elements.expressions; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Name("Stored Enchantments") +@Description(""" + The enchantments stored inside an enchanted book. This is different from enchanting the book, as for example a book + of sharpness does not deal more damage when used as a weapon. Corresponds to the minecraft:stored_enchantments data component. + Note that for example resetting the stored enchantments of an enchanted book of Sharpness III removes the sharpness enchantment, since + it is impossible to know what the original enchantments of the item were. Note also that only one entry can exist per enchantment type; + adding Sharpness III to a book with Sharpness V will convert it to a book of Sharpness III. + """) +@Example(""" + command /godbook: + trigger: + set {_item} to minecraft:enchanted_book + add mending to stored enchants of {_item} # adds mending 1 + add knockback 12 to stored enchants of {_item} + add fire aspect 3 to stored enchants of {_item} + give {_item} to player + """) +@Since("2.16") +public class ExprStoredEnchantments extends PropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprStoredEnchantments.class, EnchantmentType.class, "stored enchant[ment]s", "itemtypes", false) + .supplier(ExprStoredEnchantments::new) + .build()); + } + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) expressions[0]); + return true; + } + + @Override + protected EnchantmentType[] get(Event event, ItemType[] source) { + List types = new ArrayList<>(); + for (ItemType type : source) { + ItemMeta meta = type.getItemMeta(); + if (!(meta instanceof EnchantmentStorageMeta storageMeta)) + continue; + + for (Map.Entry entry : storageMeta.getStoredEnchants().entrySet()) { + types.add(new EnchantmentType(entry.getKey(), entry.getValue())); + } + } + + return types.toArray(EnchantmentType[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, DELETE, REMOVE, REMOVE_ALL -> CollectionUtils.array(EnchantmentType[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + ItemType[] targets = getExpr().getArray(event); + for (ItemType target : targets) { + ItemMeta rawMeta = target.getItemMeta(); + if (!(rawMeta instanceof EnchantmentStorageMeta meta)) + continue; + Map adjusted = new HashMap<>(meta.getStoredEnchants()); + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + adjusted.clear(); + + switch (mode) { + case DELETE -> {} + case ADD, SET -> { + assert delta != null; + for (Object change : delta) { + if (!(change instanceof EnchantmentType type)) + continue; + adjusted.put(type.getType(), type.getLevel()); + } + } + case REMOVE, REMOVE_ALL -> { + assert delta != null; + for (Object change : delta) { + if (!(change instanceof EnchantmentType type)) + continue; + if (type.getInternalLevel() == -1) { + adjusted.remove(type.getType()); + } else { + adjusted.remove(type.getType(), type.getLevel()); + } + } + } + default -> throw new IllegalArgumentException("Invalid change mode " + mode); + } + + for (Enchantment existing : meta.getStoredEnchants().keySet()) + meta.removeStoredEnchant(existing); + + for (Map.Entry adjustedEntry : adjusted.entrySet()) + meta.addStoredEnchant(adjustedEntry.getKey(), adjustedEntry.getValue(), true); + + target.setItemMeta(meta); + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return EnchantmentType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "stored enchantments of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java new file mode 100644 index 000000000..c5b837692 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java @@ -0,0 +1,26 @@ +package org.skriptlang.skript.bukkit.enchantments.types; + +import ch.njol.skript.classes.registry.RegistryClassInfo; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class EnchantmentClassInfo extends RegistryClassInfo { + + public EnchantmentClassInfo() { + super(Enchantment.class, RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT), + "enchantment", "enchantments"); + + user("enchantments?") + .name("Enchantment") + .description("An enchantment, e.g. 'sharpness' or 'fortune'. Unlike enchantment type " + + "this type has no level, but you usually don't need to use this type anyway.", + "NOTE: Minecraft namespaces are supported, ex: 'minecraft:basalt_deltas'.", + "This also supports custom enchantments using namespaces, ex: 'myenchants:explosive'.") + .since("1.4.6") + .before("enchantmenttype"); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentOfferClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentOfferClassInfo.java new file mode 100644 index 000000000..3ece939bd --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentOfferClassInfo.java @@ -0,0 +1,46 @@ +package org.skriptlang.skript.bukkit.enchantments.types; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.registrations.Classes; +import org.bukkit.enchantments.EnchantmentOffer; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class EnchantmentOfferClassInfo extends ClassInfo { + + public EnchantmentOfferClassInfo() { + super(EnchantmentOffer.class, "enchantmentoffer"); + user("enchant[ment][ ]offers?") + .name("Enchantment Offer") + .description("The enchantmentoffer in an enchant prepare event.") + .examples(""" + on enchant prepare: + set enchant offer 1 to sharpness 1 + set the cost of enchant offer 1 to 10 levels + """) + .since("2.5") + .parser(new EnchantmentOfferParser()); + } + + private static class EnchantmentOfferParser extends Parser { + // + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(EnchantmentOffer eo, int flags) { + return Classes.toString(eo.getEnchantment()) + " " + eo.getEnchantmentLevel(); + } + + @Override + public String toVariableNameString(EnchantmentOffer eo) { + return "offer:" + Classes.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentTypeClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentTypeClassInfo.java new file mode 100644 index 000000000..5978f2d0e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentTypeClassInfo.java @@ -0,0 +1,46 @@ +package org.skriptlang.skript.bukkit.enchantments.types; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.YggdrasilSerializer; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.util.EnchantmentType; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public class EnchantmentTypeClassInfo extends ClassInfo { + + public EnchantmentTypeClassInfo() { + super(EnchantmentType.class, "enchantmenttype"); + user("enchant(ing|ment) ?types?") + .name("Enchantment Type") + .description("An enchantment with an optional level, e.g. 'sharpness 2' or 'fortune'.") + .usage(" []") + .examples("enchant the player's tool with sharpness 5", + "helmet is enchanted with waterbreathing") + .since("1.4.6") + .parser(new EnchantmentTypeParser()) + .serializer(new YggdrasilSerializer<>()); + } + + private static class EnchantmentTypeParser extends Parser { + // + @Override + public @Nullable EnchantmentType parse(String string, ParseContext context) { + return EnchantmentType.parse(string); + } + + @Override + public String toString(EnchantmentType type, int flags) { + return type.toString(); + } + + @Override + public String toVariableNameString(EnchantmentType type) { + return type.toString(); + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/EntityModule.java b/src/main/java/org/skriptlang/skript/bukkit/entity/EntityModule.java index 5e07c604e..bdd2f43c8 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/entity/EntityModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/EntityModule.java @@ -2,16 +2,30 @@ import ch.njol.skript.Skript; import ch.njol.skript.entity.SimpleEntityData; +import ch.njol.skript.lang.util.SimpleEvent; +import com.destroystokyo.paper.event.entity.EntityPathfindEvent; +import io.papermc.paper.event.player.AsyncChatEvent; +import org.bukkit.Location; +import ch.njol.skript.registrations.Classes; import org.bukkit.entity.AbstractNautilus; +import org.bukkit.entity.Entity; import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.bukkit.entity.displays.DisplayModule; +import org.skriptlang.skript.bukkit.entity.elements.expressions.ExprPathfindingLocation; +import org.skriptlang.skript.bukkit.entity.elements.expressions.ExprPathfindingTarget; +import org.skriptlang.skript.bukkit.entity.elements.effects.EffTeleport; import org.skriptlang.skript.bukkit.entity.interactions.InteractionModule; import org.skriptlang.skript.bukkit.entity.elements.expressions.ExprDeathMessage; import org.skriptlang.skript.bukkit.entity.entitydata.NautilusData; import org.skriptlang.skript.bukkit.entity.entitydata.ZombieNautilusData; import org.skriptlang.skript.bukkit.entity.player.PlayerModule; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; +import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxRegistry; +import org.skriptlang.skript.bukkit.entity.types.TeleportFlagClassInfo; import java.util.List; @@ -29,6 +43,11 @@ public Iterable children() { ); } + @Override + protected void initSelf(SkriptAddon addon) { + Classes.registerClass(new TeleportFlagClassInfo()); + } + @Override protected void loadSelf(SkriptAddon addon) { if (Skript.classExists("org.bukkit.entity.Nautilus")) { @@ -36,8 +55,38 @@ protected void loadSelf(SkriptAddon addon) { ZombieNautilusData.register(); SimpleEntityData.addSuperEntity("any nautilus", AbstractNautilus.class); } + SyntaxRegistry syntaxRegistry = moduleRegistry(addon); + EventValueRegistry registry = addon.registry(EventValueRegistry.class); + syntaxRegistry.register(BukkitSyntaxInfos.Event.KEY, BukkitSyntaxInfos.Event.builder(SimpleEvent.class, "Pathfind") + .addDescription("Called whenever an entity tries to pathfind to a location or another entity.") + .addExample(""" + on pathfind: + broadcast "%event-entity% is about to move to %event-location%!" + """) + .addSince("2.16") + .addPattern("[entity] [start[s]] pathfind[ing]") + .addEvent(EntityPathfindEvent.class) + .build()); + + registry.register(EventValue.builder(EntityPathfindEvent.class, Location.class) + .getter(EntityPathfindEvent::getLoc) + .patterns("target location") + .build()); + + registry.register(EventValue.builder(EntityPathfindEvent.class, Entity.class) + .getter(EntityPathfindEvent::getTargetEntity) + .patterns("target entity") + .build()); + + registry.register(EventValue.builder(EntityPathfindEvent.class, Location.class) + .getter(event -> event.getEntity().getLocation()) + .build()); register(addon, + ExprDeathMessage::register, + ExprPathfindingLocation::register, + ExprPathfindingTarget::register, + EffTeleport::register, ExprDeathMessage::register ); } diff --git a/src/main/java/ch/njol/skript/effects/EffTeleport.java b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/effects/EffTeleport.java similarity index 65% rename from src/main/java/ch/njol/skript/effects/EffTeleport.java rename to src/main/java/org/skriptlang/skript/bukkit/entity/elements/effects/EffTeleport.java index aa3dc2b52..75ea5e791 100644 --- a/src/main/java/ch/njol/skript/effects/EffTeleport.java +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/effects/EffTeleport.java @@ -1,40 +1,40 @@ -package ch.njol.skript.effects; +package org.skriptlang.skript.bukkit.entity.elements.effects; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.SkriptTeleportFlag; +import ch.njol.skript.test.runner.TestMode; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.entity.types.TeleportFlagClassInfo.SkriptTeleportFlag; import ch.njol.skript.doc.*; +import ch.njol.skript.effects.Delay; import ch.njol.skript.lang.*; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.sections.EffSecSpawn.SpawnEvent; -import ch.njol.skript.test.runner.TestMode; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.util.Direction; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; -import io.papermc.lib.PaperLib; -import io.papermc.lib.environments.PaperEnvironment; import io.papermc.paper.entity.TeleportFlag; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerRespawnEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.Arrays; import java.util.Objects; import java.util.stream.Stream; @Name("Teleport") -@Description({ - "Teleport an entity to a specific location. ", - "This effect is delayed by default on Paper, meaning certain syntax such as the return effect for functions cannot be used after this effect.", - "The keyword 'force' indicates this effect will not be delayed, ", - "which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks.", - "Teleport flags are settings to retain during a teleport. Such as direction, passengers, x coordinate, etc." -}) +@Description(""" + Teleport an entity to a specific location. + This effect is delayed by default, as it waits for the chunk of the location to be loaded before teleporting. + The keyword 'force' may be used to bypass this behavior (preventing the delay), but note that it may cause lag spikes or other server performance issues when teleporting to unloaded chunks. + Teleport flags are properties to retain during a teleport, such as direction, passengers, and velocities. + """) @Example("teleport the player to {home::%uuid of player%}") @Example("teleport the attacker to the victim") @Example(""" @@ -45,40 +45,36 @@ @Since("1.0, 2.10 (flags)") public class EffTeleport extends Effect { - private static final boolean TELEPORT_FLAGS_SUPPORTED = Skript.classExists("io.papermc.paper.entity.TeleportFlag"); - private static final boolean CAN_RUN_ASYNC = PaperLib.getEnvironment() instanceof PaperEnvironment; - - static { - String extra = ""; - if (TELEPORT_FLAGS_SUPPORTED) - extra = " [[while] retaining %-teleportflags%]"; - Skript.registerEffect(EffTeleport.class, "[:force] teleport %entities% (to|%direction%) %location%" + extra); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffTeleport.class) + .supplier(EffTeleport::new) + .addPattern("[:force] teleport %entities% (to|%direction%) %location% [[while] retaining %-teleportflags%]") + .build()); } - private @Nullable Expression teleportFlags; + private boolean async; private Expression entities; private Expression location; - private boolean async; + private @Nullable Expression teleportFlags; @Override @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - entities = (Expression) exprs[0]; - location = Direction.combine((Expression) exprs[1], (Expression) exprs[2]); - async = CAN_RUN_ASYNC && !parseResult.hasTag("force"); - if (parseResult.hasTag("force")) { - Skript.warning("Folia requires all teleports to be async, thus 'force' will not work."); - } - if (TELEPORT_FLAGS_SUPPORTED) - teleportFlags = (Expression) exprs[3]; - if (getParser().isCurrentEvent(SpawnEvent.class)) { Skript.error("You cannot teleport an entity that hasn't spawned yet. Ensure you're using the location expression from the spawn section pattern."); return false; } - if (async) - getParser().setHasDelayBefore(Kleenean.UNKNOWN); // UNKNOWN because it isn't async if the chunk is already loaded. + async = !parseResult.hasTag("force"); + entities = (Expression) exprs[0]; + location = Direction.combine((Expression) exprs[1], (Expression) exprs[2]); + teleportFlags = (Expression) exprs[3]; + + if (async) { + // UNKNOWN because it isn't async if the chunk is already loaded + getParser().setHasDelayBefore(Kleenean.UNKNOWN); + } + return true; } @@ -87,21 +83,19 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye debug(event, true); TriggerItem next = getNext(); - boolean delayed = Delay.isDelayed(event); Location location = this.location.getSingle(event); if (location == null) return next; boolean unknownWorld = !location.isWorldLoaded(); - Entity[] entityArray = entities.getArray(event); // We have to fetch this before possible async execution to avoid async local variable access. + Entity[] entityArray = entities.getArray(event); if (entityArray.length == 0) return next; - if (!delayed) { + if (!Delay.isDelayed(event)) { // specific behavior for integration with certain events if (event instanceof PlayerRespawnEvent playerRespawnEvent && entityArray.length == 1 && entityArray[0].equals(playerRespawnEvent.getPlayer())) { - if (unknownWorld) - return next; - playerRespawnEvent.setRespawnLocation(location); + if (!unknownWorld) + playerRespawnEvent.setRespawnLocation(location); return next; } @@ -114,6 +108,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return next; } } + if (unknownWorld) { // we can't fetch the chunk without a world if (entityArray.length == 1) { // if there's 1 thing we can borrow its world Entity entity = entityArray[0]; @@ -127,8 +122,16 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } } + final TeleportFlag[] teleportFlags; + if (this.teleportFlags == null) { + teleportFlags = new TeleportFlag[0]; + } else { + teleportFlags = this.teleportFlags.stream(event) + .flatMap(teleportFlag -> Stream.of(teleportFlag.getTeleportFlags())) + .toArray(TeleportFlag[]::new); + } + if (!async) { - SkriptTeleportFlag[] teleportFlags = this.teleportFlags == null ? null : this.teleportFlags.getArray(event); for (Entity entity : entityArray) { teleport(entity, location, teleportFlags); } @@ -137,12 +140,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye final Location fixed = location; Object localVars = Variables.removeLocals(event); - - // This will either fetch the chunk instantly if on Spigot or already loaded or fetch it async if on Paper. - PaperLib.getChunkAtAsync(location).thenAccept(chunk -> { + fixed.getWorld().getChunkAtAsync(fixed).thenAccept(ignored -> { Delay.addDelayedEvent(event); - // The following is now on the main thread - SkriptTeleportFlag[] teleportFlags = this.teleportFlags == null ? null : this.teleportFlags.getArray(event); for (Entity entity : entityArray) { teleport(entity, fixed, teleportFlags); } @@ -166,6 +165,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Variables.removeLocals(event); // Clean up local vars, we may be exiting now SkriptTimings.stop(timing); }); + return null; } @@ -176,20 +176,19 @@ protected void execute(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug) - .append("teleport", entities, "to", location); - if (teleportFlags != null) - builder.append("retaining", teleportFlags); - return builder.toString(); + return new SyntaxStringBuilder(event, debug) + .append("teleport", entities, "to", location) + .appendIf(teleportFlags != null, "retaining", teleportFlags) + .toString(); } - private void teleport(@NotNull Entity entity, @NotNull Location location, SkriptTeleportFlag... skriptTeleportFlags) { + private void teleport(@NotNull Entity entity, @NotNull Location location, TeleportFlag[]... skriptTeleportFlags) { if (location.getWorld() == null) { location = location.clone(); location.setWorld(entity.getWorld()); } - if (!TELEPORT_FLAGS_SUPPORTED || skriptTeleportFlags == null) { + if (skriptTeleportFlags == null) { if (TestMode.ENABLED) { entity.teleport(location); } else { @@ -199,8 +198,8 @@ private void teleport(@NotNull Entity entity, @NotNull Location location, Skript } Stream teleportFlags = Arrays.stream(skriptTeleportFlags) - .flatMap(teleportFlag -> Stream.of(teleportFlag.getTeleportFlags())) - .filter(Objects::nonNull); + .flatMap(Stream::of) + .filter(Objects::nonNull); if (TestMode.ENABLED) { entity.teleport(location, teleportFlags.toArray(TeleportFlag[]::new)); return; diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingLocation.java b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingLocation.java new file mode 100644 index 000000000..93061d25b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingLocation.java @@ -0,0 +1,73 @@ +package org.skriptlang.skript.bukkit.entity.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import com.destroystokyo.paper.event.entity.EntityPathfindEvent; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Pathfinding Target Location") +@Description("The location that the entity is pathfinding towards.") +@Example(""" + on pathfind: + if the pathfinding target location is within radius 5 of {mylocation}: + broadcast "A mob tried to pathfind near a forbidden location!" + cancel event + """) +@Since("2.16") +@Events("pathfind") +public class ExprPathfindingLocation extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprPathfindingLocation.class, Location.class) + .supplier(ExprPathfindingLocation::new) + .priority(SyntaxInfo.SIMPLE) + .addPattern("[the] path[ ]finding target location") + .build()); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return new Class[]{EntityPathfindEvent.class}; + } + + @Override + protected Location[] get(Event event) { + if (!(event instanceof EntityPathfindEvent pathfindEvent)) + return null; + Location location = pathfindEvent.getLoc(); + return new Location[]{location}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Location.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the pathfinding location"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingTarget.java b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingTarget.java new file mode 100644 index 000000000..dad36e806 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/elements/expressions/ExprPathfindingTarget.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.entity.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import com.destroystokyo.paper.event.entity.EntityPathfindEvent; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Pathfinding Target Entity") +@Description("The target entity that another entity is pathfinding towards in a pathfinding event.") +@Example(""" + on pathfind: + if the pathfinding target entity is a villager: + broadcast "I suspect a zombie is going towards a villager right now.." + """) +@Since("2.16") +@Events("pathfind") +public class ExprPathfindingTarget extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprPathfindingTarget.class, Entity.class) + .supplier(ExprPathfindingTarget::new) + .priority(SyntaxInfo.SIMPLE) + .addPattern("[the] path[ ]finding target [entity]") + .build()); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return new Class[]{EntityPathfindEvent.class}; + } + + @Override + protected Entity[] get(Event event) { + if (!(event instanceof EntityPathfindEvent pathfindEvent)) + return null; + Entity target = pathfindEvent.getTargetEntity(); + return target != null ? new Entity[]{target} : new Entity[0]; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Entity.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the pathfinding target"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java index 74bf611cb..10704eda2 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/PlayerModule.java @@ -9,6 +9,7 @@ import org.skriptlang.skript.bukkit.entity.player.elements.effects.*; import org.skriptlang.skript.bukkit.entity.player.elements.events.*; import org.skriptlang.skript.bukkit.entity.player.elements.expressions.*; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; import org.skriptlang.skript.registration.SyntaxRegistry; @@ -20,17 +21,22 @@ public PlayerModule(AddonModule parentModule) { @Override protected void loadSelf(SkriptAddon addon) { + EventValueRegistry eventValueRegistry = addon.registry(EventValueRegistry.class); + register(addon, EffBan::register, EffKick::register, + syntaxRegistry -> EvtPlayerGameModeChange.register(syntaxRegistry, eventValueRegistry), ExprChatFormat::register, ExprChatMessage::register, ExprChatRecipients::register, + ExprGameMode::register, ExprJoinMessage::register, ExprKickMessage::register, ExprOnScreenKickMessage::register, ExprPlayerListHeaderFooter::register, ExprPlayerListName::register, + ExprPlayerListPriority::register, ExprQuitMessage::register ); if (Skript.classExists("io.papermc.paper.event.player.PlayerPickBlockEvent")) { @@ -41,6 +47,7 @@ protected void loadSelf(SkriptAddon addon) { } SyntaxRegistry syntaxRegistry = moduleRegistry(addon); + syntaxRegistry.register(BukkitSyntaxInfos.Event.KEY, BukkitSyntaxInfos.Event.builder(SimpleEvent.class, "Chat") .addDescription("Called whenever a player chats.", "Use chat format to change message format.", diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerGameModeChange.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerGameModeChange.java new file mode 100644 index 000000000..1625dac94 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/events/EvtPlayerGameModeChange.java @@ -0,0 +1,70 @@ +package org.skriptlang.skript.bukkit.entity.player.elements.events; + +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import org.bukkit.GameMode; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; +import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxRegistry; + +public class EvtPlayerGameModeChange extends SkriptEvent { + + public static void register(SyntaxRegistry syntaxRegistry, EventValueRegistry registry) { + syntaxRegistry.register(BukkitSyntaxInfos.Event.KEY, BukkitSyntaxInfos.Event.builder(EvtPlayerGameModeChange.class, "Player GameMode Change") + .supplier(EvtPlayerGameModeChange::new) + .addEvent(PlayerGameModeChangeEvent.class) + .addPatterns("[player] game[ ]mode change [to %gamemode%]") + .addDescription(""" + Called when a player's gamemode is changed. See gamemode. + """) + .addExample(""" + on game mode change: + if event-gamemode is creative: + send "Your gamemode is now creative! Totally don't go spawning items in.." + """) + .addExample(""" + on game mode change: + send "Wow! Your gamemode changed!" + """) + .addSince("1.0") + .build()); + + registry.register(EventValue.builder(PlayerGameModeChangeEvent.class, GameMode.class) + .getter(PlayerGameModeChangeEvent::getNewGameMode) + .patterns("gamemode") + .build()); + } + + private Literal gamemode; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Literal[] literals, int i, ParseResult parseResult) { + gamemode = (Literal) literals[0]; + return true; + } + + @Override + public boolean check(Event event) { + if (gamemode == null) + return true; + GameMode gamemode = this.gamemode.getSingle(event); + PlayerGameModeChangeEvent playerEvent = (PlayerGameModeChangeEvent) event; + return playerEvent.getNewGameMode().equals(gamemode); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("player gamemode change") + .appendIf(gamemode != null, "to", gamemode) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprChatFormat.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprChatFormat.java index 1ffdc6675..271a75261 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprChatFormat.java +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprChatFormat.java @@ -20,16 +20,20 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; import org.skriptlang.skript.registration.SyntaxInfo; import org.skriptlang.skript.registration.SyntaxRegistry; +import java.util.concurrent.atomic.AtomicBoolean; + @Name("Chat Format") -@Description({ - "Can be used to modify the chat format.", - "The sender of a message is represented by [player] or [sender].", - "The message is represented by [message] or [msg].", -}) -@Example("set the chat format to \"[player]: [message]\"") +@Description(""" + Can be used to modify the chat format. + The sender of a message is represented by player. + The message is represented by message. + """) +@Example("set the chat format to \"%player%: %message%\"") @Since("2.2-dev31") @Events("chat") public class ExprChatFormat extends SimpleExpression implements EventRestrictedSyntax { @@ -41,6 +45,9 @@ public static void register(SyntaxRegistry syntaxRegistry) { .build()); } + private boolean suppressDeprecatedWarning; + private boolean containsPlaceholders; + @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { var stackIterator = getParser().getParsingStack().iterator(); @@ -48,6 +55,9 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is Skript.error("'" + toString(null, false) + "' can only be changed, not obtained"); return false; } + Script script = getParser().getCurrentScript(); + if (script != null) + suppressDeprecatedWarning = (script.suppressesWarning(ScriptWarning.DEPRECATED_SYNTAX)); return true; } @@ -83,12 +93,19 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { return; } - asyncChatEvent.renderer(ChatRenderer.viewerUnaware(((source, sourceDisplayName, message) -> + asyncChatEvent.renderer(ChatRenderer.viewerUnaware((source, sourceDisplayName, message) -> ((Component) delta[0]).replaceText(TextReplacementConfig.builder() .match("(?i)\\[(player|sender|message|msg)]") - .replacement(((matchResult, builder) -> - matchResult.group(1).startsWith("m") ? message : sourceDisplayName)) - .build())))); + .replacement((matchResult, builder) -> { + containsPlaceholders = true; + return matchResult.group(1).startsWith("m") ? message : sourceDisplayName; + }) + .build()))); + + if (containsPlaceholders && !suppressDeprecatedWarning) { + containsPlaceholders = false; + warning("Using [player], [sender], [message] and [msg] is deprecated and scheduled for removal. Please use the equivalent expressions, such as '%player%' and '%message%'."); + } } @Override diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprGameMode.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprGameMode.java new file mode 100644 index 000000000..27bc3c398 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprGameMode.java @@ -0,0 +1,109 @@ +package org.skriptlang.skript.bukkit.entity.player.elements.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.effects.Delay; +import ch.njol.skript.expressions.base.PropertyExpression; +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 ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("Game Mode") +@Description(""" + The gamemode of a player. See Gamemodes + """) +@Example("player's gamemode is survival") +@Example("set the player's gamemode to creative") +@Example("set gamemode of player to adventure") +@Since("1.0") +public class ExprGameMode extends PropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprGameMode.class, + GameMode.class, + "[the] game[ ]mode", + "players", + false + ) + .supplier(ExprGameMode::new) + .build() + ); + } + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + setExpr((Expression) expressions[0]); + return true; + } + + @Override + protected GameMode[] get(Event event, Player[] source) { + Player eventPlayer; + if (getTime() >= 0 && event instanceof PlayerGameModeChangeEvent playerEvent && !Delay.isDelayed(event)) { + eventPlayer = playerEvent.getPlayer(); + } else { + eventPlayer = null; + } + return get(source, player -> { + if (player == eventPlayer) + return ((PlayerGameModeChangeEvent) event).getNewGameMode(); + return player.getGameMode(); + }); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.RESET) + return CollectionUtils.array(GameMode.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + GameMode gamemode = delta == null ? Bukkit.getDefaultGameMode() : (GameMode) delta[0]; + List players = getExpr().stream(event).toList(); + if (getTime() >= 0 && event instanceof PlayerGameModeChangeEvent playerEvent && players.contains(playerEvent.getPlayer()) && !Delay.isDelayed(event)) { + if (playerEvent.getNewGameMode() != gamemode) + playerEvent.setCancelled(true); + } + for (Player player : players) { + player.setGameMode(gamemode); + } + } + + @Override + public Class getReturnType() { + return GameMode.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("the gamemode of", getExpr()) + .toString(); + } + + @Override + public boolean setTime(int time) { + return super.setTime(time, PlayerGameModeChangeEvent.class); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprPlayerListPriority.java b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprPlayerListPriority.java new file mode 100644 index 000000000..7a4bdeb9b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/player/elements/expressions/ExprPlayerListPriority.java @@ -0,0 +1,85 @@ +package org.skriptlang.skript.bukkit.entity.player.elements.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Keywords; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Player List Priority") +@Description(""" + The priority of the player in the player list in the tab menu. + Used to sort players on the tab list. Lowest priority is at the bottom of tab and highest priority is at the top. + If 2 players have same priority then they will be sorted A-Z (but still be above those with lower priority). + """) +@Example(""" + on join: + player has permission "group.mod" + set the player's tab list priority to 5 + """) +@Since("2.16") +@Keywords({"tablist", "tab list"}) +public class ExprPlayerListPriority extends SimplePropertyExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprPlayerListPriority.class, Integer.class, + "(player|tab)[ ]list priority", "players", false) + .supplier(ExprPlayerListPriority::new) + .build()); + } + + @Override + public Integer convert(Player player) { + return player.getPlayerListOrder(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, REMOVE, SET, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int amount = delta == null ? 0 : (Integer) delta[0]; + switch (mode) { + case ADD -> { + for (Player player : getExpr().getArray(event)) { + player.setPlayerListOrder(Math.max(0, (int) Math2.addClamped(player.getPlayerListOrder(), amount))); + } + } + case REMOVE -> { + for (Player player : getExpr().getArray(event)) { + player.setPlayerListOrder(Math.max(0, (int) Math2.addClamped(player.getPlayerListOrder(), -amount))); + } + } + case SET, RESET -> { + amount = Math.max(0, amount); + for (Player player : getExpr().getArray(event)) { + player.setPlayerListOrder(amount); + } + } + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "player list priority"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/types/TeleportFlagClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/entity/types/TeleportFlagClassInfo.java new file mode 100644 index 000000000..ced03094b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/types/TeleportFlagClassInfo.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.entity.types; + +import ch.njol.skript.Skript; +import org.skriptlang.skript.bukkit.entity.types.TeleportFlagClassInfo.SkriptTeleportFlag; +import ch.njol.skript.classes.EnumClassInfo; +import io.papermc.paper.entity.TeleportFlag; +import io.papermc.paper.entity.TeleportFlag.EntityState; +import io.papermc.paper.entity.TeleportFlag.Relative; +import org.skriptlang.skript.lang.script.ScriptWarning; + +public class TeleportFlagClassInfo extends EnumClassInfo { + + private static final boolean IS_ENTITY_STATE_DEPRECATED = Skript.isRunningMinecraft(1, 21, 10); + + public TeleportFlagClassInfo() { + super(SkriptTeleportFlag.class, "teleportflag", "teleport flags", flag -> { + switch (flag) { + case RETAIN_PITCH, RETAIN_YAW -> ScriptWarning.printDeprecationWarning( + "It is no longer possible to individually retain yaw or pitch velocities." + + " Use 'yaw and pitch velocity' to retain both instead."); + case RETAIN_OPEN_INVENTORY -> { + if (IS_ENTITY_STATE_DEPRECATED) { + ScriptWarning.printDeprecationWarning( + "Inventories are no longer closed on teleportation, meaning this flag is no longer necessary."); + } + } + case RETAIN_PASSENGERS -> { + if (IS_ENTITY_STATE_DEPRECATED) { + ScriptWarning.printDeprecationWarning( + "Passengers are no longer removed on teleportation, meaning this flag is no longer necessary."); + } + } + case RETAIN_VEHICLE -> { + if (IS_ENTITY_STATE_DEPRECATED) { + ScriptWarning.printDeprecationWarning( + "The 'retain vehicle' flag has no functionality on modern versions due to technical limitations with the client."); + } + } + } + }); + this.user("teleport ?flags?") + .name("Teleport Flag") + .description("Teleport Flags are settings to retain during a teleport.") + .since("2.10"); + } + + public enum SkriptTeleportFlag { + + RETAIN_OPEN_INVENTORY(EntityState.RETAIN_OPEN_INVENTORY), + RETAIN_PASSENGERS(EntityState.RETAIN_PASSENGERS), + RETAIN_VEHICLE(EntityState.RETAIN_VEHICLE), + RETAIN_DIRECTION(Relative.VELOCITY_ROTATION), + RETAIN_PITCH(Relative.PITCH), + RETAIN_YAW(Relative.YAW), + RETAIN_MOVEMENT(Relative.VELOCITY_X, Relative.VELOCITY_Y, Relative.VELOCITY_Z), + RETAIN_X(Relative.VELOCITY_X), + RETAIN_Y(Relative.VELOCITY_Y), + RETAIN_Z(Relative.VELOCITY_Z); + + final TeleportFlag[] teleportFlags; + + SkriptTeleportFlag(TeleportFlag... teleportFlags) { + this.teleportFlags = teleportFlags; + } + + public TeleportFlag[] getTeleportFlags() { + return teleportFlags; + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookPages.java b/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookPages.java index 2c4188a4b..dd73850b1 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookPages.java +++ b/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookPages.java @@ -13,16 +13,18 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.event.Event; import org.bukkit.inventory.meta.BookMeta; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.registration.SyntaxInfo; import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; @Name("Book Pages") @@ -40,6 +42,26 @@ @Since("2.2-dev31, 2.7 (changers)") public class ExprBookPages extends SimpleExpression { + @SuppressWarnings("ConstantValue") // true on 26.1 and older + private static final boolean EXTENDS_ADVENTURE_BOOK = Book.class.isAssignableFrom(BookMeta.class); + + public static @Unmodifiable List getPages(BookMeta bookMeta) { + if (EXTENDS_ADVENTURE_BOOK) { + //noinspection ConstantConditions + return ((Book) (Object) bookMeta).pages(); + } + return bookMeta.pages(); + } + + public static void setPages(BookMeta bookMeta, List pages) { + if (EXTENDS_ADVENTURE_BOOK) { + //noinspection ConstantConditions, ResultOfMethodCallIgnored - modifies in place despite contract + ((Book) (Object) bookMeta).pages(pages); + } else { + bookMeta.pages(pages); + } + } + public static void register(SyntaxRegistry syntaxRegistry) { syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprBookPages.class, Component.class) .supplier(ExprBookPages::new) @@ -77,7 +99,7 @@ protected Component[] get(Event event) { return new Component[0]; } if (isAllPages()) { - pages.addAll(bookMeta.pages()); + pages.addAll(getPages(bookMeta)); } else { Integer pageNumber = this.pageNumber.getSingle(event); if (pageNumber == null) { @@ -104,15 +126,18 @@ protected Component[] get(Event event) { @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - int pageNumber = isAllPages() ? -1 : this.pageNumber.getOptionalSingle(event).orElse(-1); - List newPages = delta == null ? Collections.emptyList() : new ArrayList<>(delta.length); + int pageNumber = -1; + if (!isAllPages()) { + pageNumber = this.pageNumber.getOptionalSingle(event).orElse(-1); + if (pageNumber <= 0) { + return; + } + } + List newPages = delta == null ? List.of() : new ArrayList<>(delta.length); if (delta != null) { for (Object page : delta) { newPages.add((Component) page); } - if (pageNumber != -1 && newPages.isEmpty()) { // not setting this page to anything, exit early - return; - } } for (ItemType book : books.getArray(event)) { @@ -122,21 +147,34 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (isAllPages()) { switch (mode) { - case SET, DELETE, RESET -> //noinspection ResultOfMethodCallIgnored - modifies in place despite contract - bookMeta.pages(newPages); + case SET, DELETE, RESET -> setPages(bookMeta, newPages); case ADD -> bookMeta.addPages(newPages.toArray(new Component[0])); default -> throw new IllegalStateException(); } } else { switch (mode) { - case SET -> bookMeta.page(pageNumber, newPages.getFirst()); + case SET -> { + if (pageNumber > bookMeta.getPageCount()) { + Component[] pages = new Component[pageNumber - bookMeta.getPageCount()]; + Arrays.fill(pages, Component.empty()); + bookMeta.addPages(pages); + } + bookMeta.page(pageNumber, newPages.getFirst()); + } case DELETE -> { - List pages = new ArrayList<>(bookMeta.pages()); - pages.remove(pageNumber); - //noinspection ResultOfMethodCallIgnored - modifies in place despite contract - bookMeta.pages(pages); + if (pageNumber > bookMeta.getPageCount()) { + break; + } + List pages = new ArrayList<>(getPages(bookMeta)); + pages.remove(pageNumber - 1); + setPages(bookMeta, pages); + } + case RESET -> { + if (pageNumber > bookMeta.getPageCount()) { + continue; + } + bookMeta.page(pageNumber, Component.empty()); } - case RESET -> bookMeta.page(pageNumber, Component.empty()); default -> throw new IllegalStateException(); } } diff --git a/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookTitle.java b/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookTitle.java index a9366b80d..55f69b13b 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookTitle.java +++ b/src/main/java/org/skriptlang/skript/bukkit/item/book/elements/expressions/ExprBookTitle.java @@ -7,11 +7,15 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import net.kyori.adventure.text.Component; import org.bukkit.event.Event; import org.bukkit.inventory.meta.BookMeta; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.script.ScriptWarning; import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Book Title") @@ -21,6 +25,7 @@ message "You finished your book titled %title of event-item%" """) @Since("2.2-dev31") +@Deprecated(since = "2.16", forRemoval = true) public class ExprBookTitle extends SimplePropertyExpression { public static void register(SyntaxRegistry syntaxRegistry) { @@ -30,6 +35,12 @@ public static void register(SyntaxRegistry syntaxRegistry) { .build()); } + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + ScriptWarning.printDeprecationWarning("This element is deprecated and scheduled for removal in a future release. Consider using 'title of '" + "" + exprs[0].toString(null, false) + "' instead."); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + @Override public Component convert(ItemType item) { if (item.getItemMeta() instanceof BookMeta bookMeta && bookMeta.hasTitle()) { diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java b/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java index e778d8681..8557a4c9d 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java @@ -17,10 +17,12 @@ protected void loadSelf(SkriptAddon addon) { register(addon, EffRotate::register, ExprBroadcastMessage::register, + ExprColorOf::register, ExprItemOfEntity::register, ExprMOTD::register, ExprQuaternionAxisAngle::register, ExprRotate::register, + ExprSkullTexture::register, ExprTextOf::register, ExprWithYawPitch::register ); diff --git a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprColorOf.java similarity index 85% rename from src/main/java/ch/njol/skript/expressions/ExprColorOf.java rename to src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprColorOf.java index 04f373656..8bb67aeef 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprColorOf.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.misc.elements.expressions; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; @@ -10,6 +10,7 @@ import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Color; import ch.njol.skript.util.ColorRGB; import ch.njol.skript.util.SkriptColor; @@ -20,6 +21,8 @@ import org.bukkit.block.Banner; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BossBar; import org.bukkit.entity.Display; import org.bukkit.entity.Entity; import org.bukkit.entity.Item; @@ -30,7 +33,9 @@ import org.bukkit.material.MaterialData; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.bossbar.BossBarUtils; import org.skriptlang.skript.bukkit.entity.displays.DisplayData; +import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.ArrayList; import java.util.List; @@ -49,14 +54,14 @@ message "This wool block is <%color of block%>%color of block%!" set the color of the block to black """) -@Since("1.2, 2.10 (displays)") +@Since("1.2, 2.10 (displays), 2.16 (boss bars)") public class ExprColorOf extends PropertyExpression { - static { - String types = "blocks/itemtypes/entities/fireworkeffects/potioneffecttypes"; - if (Skript.isRunningMinecraft(1, 19, 4)) - types += "/displays"; - register(ExprColorOf.class, Color.class, "colo[u]r[s]", types); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprColorOf.class, Color.class, "colo[u]r[s]", "blocks/itemtypes/entities/fireworkeffects/potioneffecttypes/displays/bossbars", false) + .supplier(ExprColorOf::new) + .build()); } @Override @@ -67,6 +72,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Color[] get(Event event, Object[] source) { + if (source instanceof FireworkEffect[]) { List colors = new ArrayList<>(); for (FireworkEffect effect : (FireworkEffect[]) source) { @@ -87,6 +93,9 @@ protected Color[] get(Event event, Object[] source) { return null; return ColorRGB.fromBukkitColor(bukkitColor); } + if (object instanceof BossBar bar) { + return BossBarUtils.rgbFromBarColor(bar.getColor()); + } return getColor(object); }); } @@ -98,7 +107,7 @@ protected Color[] get(Event event, Object[] source) { if (expression.canReturn(FireworkEffect.class)) return CollectionUtils.array(Color[].class); - if ((mode == ChangeMode.RESET || mode == ChangeMode.SET) && expression.canReturn(Display.class)) + if ((mode == ChangeMode.RESET || mode == ChangeMode.SET) && expression.canReturn(Display.class) || expression.canReturn(BossBar.class)) return CollectionUtils.array(Color.class); if (mode == ChangeMode.SET && @@ -116,7 +125,18 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { Consumer displayChanger = getDisplayChanger(mode, colors); Consumer fireworkChanger = getFireworkChanger(mode, colors); for (Object object : getExpr().getArray(event)) { - if (object instanceof TextDisplay display) { + if (object instanceof BossBar bar) { + if (mode == ChangeMode.SET) { + BarColor barColor = BossBarUtils.nearest(colors[0]); + if (barColor == null) { + error("Could not round to a color that is similar to " + Classes.toString(colors[0])); + } else { + bar.setColor(barColor); + } + } else if (mode == ChangeMode.RESET) { + bar.setColor(BarColor.WHITE); + } + } else if (object instanceof TextDisplay display) { displayChanger.accept(display); } else if (object instanceof FireworkEffect effect) { fireworkChanger.accept(effect); @@ -208,7 +228,7 @@ private Consumer getFireworkChanger(ChangeMode mode, Color @Null private @Nullable Colorable getColorable(Object colorable) { if (colorable instanceof Item || colorable instanceof ItemType) { ItemStack item = colorable instanceof Item ? - ((Item) colorable).getItemStack() : ((ItemType) colorable).getRandom(); + ((Item) colorable).getItemStack() : ((ItemType) colorable).getRandom(); if (item == null) return null; diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprSkullTexture.java b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprSkullTexture.java new file mode 100644 index 000000000..456156722 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprSkullTexture.java @@ -0,0 +1,87 @@ +package org.skriptlang.skript.bukkit.misc.elements.expressions; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +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 com.destroystokyo.paper.profile.ProfileProperty; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.SkullMeta; +import com.destroystokyo.paper.profile.PlayerProfile; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.UUID; + +@Name("Skull Texture") +@Description(""" + The skull texture of a player head. This allows you to give a skull a custom texture (e.g. instead of it being a Steve head, it's Notch's head). + The texture input is a base64 string containing the texture data to use (https://minecraft-heads.com is one site that provides easy access to base64 texture strings). + Resetting the texture of a skull will make it look like a Steve/Alex head. + """) +@Example("set the skull texture of {_i} to \"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTM4NmRmZDc0Y2JhZmJkMWRiZTQ3OWY1ZTAzNzRjMDliZjJlYjRlMzg2NjExZmM0ZmM2OTlmMDJlY2E0ZGQyYyJ9fX0=\"") +@Since("2.16") +public class ExprSkullTexture extends SimplePropertyExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprSkullTexture.class, String.class, "(skull|head) texture", "itemtypes", false) + .supplier(ExprSkullTexture::new) + .build()); + } + + @Override + public @Nullable String convert(ItemType item) { + if (!(item.getItemMeta() instanceof SkullMeta meta)) + return null; + PlayerProfile profile = meta.getPlayerProfile(); + if (profile == null) + return null; + return profile.getProperties().stream() + .filter(property -> property.getName().equals("textures")) + .findFirst() + .map(ProfileProperty::getValue) + .orElse(null); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET -> CollectionUtils.array(String.class); + case DELETE, RESET -> CollectionUtils.array(); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + PlayerProfile profile = null; + if (delta != null) { + profile = Bukkit.createProfile(UUID.randomUUID()); + profile.setProperty(new ProfileProperty("textures", (String) delta[0])); + } + for (ItemType item : getExpr().getArray(event)) { + if (item.getItemMeta() instanceof SkullMeta meta) { + meta.setPlayerProfile(profile); + item.setItemMeta(meta); + } + } + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + protected String getPropertyName() { + return "skull texture"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java b/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java index 631554e07..da3d82029 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java @@ -137,7 +137,7 @@ protected boolean canBeInstantiated() { } else { throw new IllegalStateException("Potion effect registry does not exist"); } - Classes.registerClass(new RegistryClassInfo<>(PotionEffectType.class, petRegistry, "potioneffecttype", "potion effect types", false) + Classes.registerClass(new RegistryClassInfo<>(PotionEffectType.class, petRegistry, "potioneffecttype", "potion effect types") .user("potion ?effect ?types?") .name("Potion Effect Type") .description("A potion effect type, e.g. 'strength' or 'swiftness'.") diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java index 463516276..b7b930ab6 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionDuration.java @@ -11,6 +11,7 @@ import ch.njol.util.Math2; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.bukkit.potion.util.PotionUtils; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; @@ -63,7 +64,8 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { * @param change The timespan delta. * @param mode The mode of change to perform. */ - static void changeSafe(SkriptPotionEffect potionEffect, Timespan change, ChangeMode mode) { + @ApiStatus.Internal + public static void changeSafe(SkriptPotionEffect potionEffect, Timespan change, ChangeMode mode) { Timespan duration; if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { duration = change; diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java index 3f119c047..1e34fa50d 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffect.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.bukkit.potion.elements.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -15,20 +14,17 @@ import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffects.State; -import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider.RetrievalState; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; -import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.skriptlang.skript.registration.SyntaxRegistry; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; @Name("Potion Effect of Entity/Item") @@ -50,23 +46,23 @@ public class ExprPotionEffect extends PropertyExpression types; - private State state; + private RetrievalState state; @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { //noinspection unchecked types = (Expression) expressions[matchedPattern % 2]; setExpr(expressions[(matchedPattern + 1) % 2]); - state = State.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); - if (state.includesHidden() && ItemType.class.isAssignableFrom(getExpr().getReturnType())) { - Skript.error("Items (such as potions or stews) do not have hidden effects"); + state = RetrievalState.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); + if (state.includesHidden() && !getExpr().canReturn(Entity.class)) { + Skript.error("Only living entities have hidden effects"); return false; } return true; @@ -77,34 +73,8 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { List potionEffects = new ArrayList<>(); PotionEffectType[] types = this.types.getArray(event); for (Object object : source) { - if (object instanceof LivingEntity livingEntity) { - for (PotionEffectType type : types) { - PotionEffect potionEffect = livingEntity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - if (state.includesActive()) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, livingEntity)); - } - if (state.includesHidden()) { - PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); - while (hiddenEffect != null) { - // do not set source for hidden effects - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); - hiddenEffect = hiddenEffect.getHiddenPotionEffect(); - } - } - } - } else if (object instanceof ItemType itemType) { - for (PotionEffect effect : PotionUtils.getPotionEffects(itemType)) { - for (PotionEffectType type : types) { - if (type.equals(effect.getType())) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, itemType)); - break; - } - } - } - } + potionEffects.addAll(PotionEffectProvider.of(object, this::error) + .get(types, state)); } return potionEffects.toArray(new SkriptPotionEffect[0]); } @@ -130,23 +100,15 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { switch (mode) { case DELETE, RESET -> { for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - reset(entity, types); - } else if (holder instanceof ItemType itemType) { - PotionUtils.removePotionEffects(itemType, types); - } + PotionEffectProvider.of(holder, this::error) + .clear(types, state); } } case ADD, REMOVE -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - modify(entity, types, delta, mode); - } else if (holder instanceof ItemType itemType) { - if (delta[0] instanceof Timespan change) { - modify(itemType, types, change, mode); - } - } + PotionEffectProvider.of(holder, this::error) + .modify(types, state, delta, mode); } } default -> { @@ -155,98 +117,6 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { } } - private void reset(LivingEntity entity, PotionEffectType[] types) { - if (state == State.ACTIVE) { // preserve hidden effects - for (PotionEffectType type : types) { - PotionEffect potionEffect = entity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); - entity.removePotionEffect(type); - entity.addPotionEffects(hiddenEffects); - } - } else if (state == State.HIDDEN) { // preserve active effect - for (PotionEffectType type : types) { - PotionEffect original = entity.getPotionEffect(type); - entity.removePotionEffect(type); - if (original != null) { - // applying a potion effect ignores the hidden effect value - entity.addPotionEffect(original); - } - } - } else { - for (PotionEffectType type : types) { - entity.removePotionEffect(type); - } - } - } - - private void modify(LivingEntity entity, PotionEffectType[] types, Object[] delta, ChangeMode mode) { - for (PotionEffectType type : types) { - PotionEffect potionEffect = entity.getPotionEffect(type); - if (potionEffect == null) { - continue; - } - - Deque finalEffects; // effects to be applied - Deque effects; // effects to be filtered - boolean madeChanges = false; - - if (state.includesHidden()) { // modify hidden effects - finalEffects = new ArrayDeque<>(); - effects = PotionUtils.getHiddenEffects(potionEffect); - } else { // otherwise, simply preserve the hidden effects - finalEffects = PotionUtils.getHiddenEffects(potionEffect); - effects = new ArrayDeque<>(); - } - - if (state.includesActive()) { // need to modify the active effect too - effects.addLast(potionEffect); - } - - // filter effects - effectLoop: for (PotionEffect effect : effects) { - SkriptPotionEffect skriptEffect = SkriptPotionEffect.fromBukkitEffect(effect); - for (Object object : delta) { - if (object instanceof Timespan timespan) { - ExprPotionDuration.changeSafe(skriptEffect, timespan, mode); - madeChanges = true; - } else if (object instanceof SkriptPotionEffect base) { - if (base.matchesQualities(effect)) { // remove this effect - madeChanges = true; - continue effectLoop; - } - } - } - // since we iterate most to least hidden, we need to preserve that order - finalEffects.addLast(skriptEffect.asBukkitPotionEffect()); - } - if (!madeChanges) { // no potion effects were modified, don't reapply effects - return; - } - - if (!state.includesActive()) { // if we didn't modify the active effect, we need to push it now - effects.addLast(potionEffect); - } - - entity.removePotionEffect(type); - entity.addPotionEffects(finalEffects); - } - } - - private void modify(ItemType itemType, PotionEffectType[] types, Timespan change, ChangeMode mode) { - for (PotionEffect effect : PotionUtils.getPotionEffects(itemType)) { - for (PotionEffectType type : types) { - if (type.equals(effect.getType())) { - // use SkriptPotionEffect source system to handle removal and application - ExprPotionDuration.changeSafe(SkriptPotionEffect.fromBukkitEffect(effect, itemType), change, mode); - break; - } - } - } - } - @Override public boolean isSingle() { return types.isSingle() && !state.includesHidden(); @@ -272,7 +142,7 @@ public String toString(@Nullable Event event, boolean debug) { } @ApiStatus.Internal - public State getState() { + public RetrievalState getState() { return state; } diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java index fb177f07d..f7729791c 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/elements/expressions/ExprPotionEffects.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.bukkit.potion.elements.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -13,19 +12,18 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider.RetrievalState; import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; -import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; import java.util.List; @Name("Potion Effects of Entity/Item") @@ -48,52 +46,20 @@ public class ExprPotionEffects extends PropertyExpression ACTIVE; // explicitly active - case "hidden" -> HIDDEN; // explicitly hidden - case "both" -> BOTH; // explicitly active and hidden - default -> UNSET; // implicitly active for get, implicitly active and hidden for delete/reset - }; - } - - boolean includesActive() { - return this != State.HIDDEN; - } - - public boolean includesHidden() { - return this == State.HIDDEN || this == State.BOTH; - } - - public String displayName() { - return switch (this) { - case UNSET -> ""; - case ACTIVE -> "active"; - case HIDDEN -> "hidden"; - case BOTH -> "active and hidden"; - }; - } - } - - private State state; + private RetrievalState state; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { setExpr(exprs[0]); - state = State.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); - if (state.includesHidden() && ItemType.class.isAssignableFrom(getExpr().getReturnType())) { - Skript.error("Items (such as potions or stews) do not have hidden effects"); + state = RetrievalState.fromParseTag(parseResult.tags.isEmpty() ? "" : parseResult.tags.getFirst()); + if (state.includesHidden() && !getExpr().canReturn(Entity.class)) { + Skript.error("Only living entities have hidden effects"); return false; } return true; @@ -103,25 +69,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected SkriptPotionEffect[] get(Event event, Object[] source) { List potionEffects = new ArrayList<>(); for (Object object : source) { - if (object instanceof LivingEntity livingEntity) { - for (PotionEffect potionEffect : livingEntity.getActivePotionEffects()) { - if (state.includesActive()) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, livingEntity)); - } - if (state.includesHidden()) { - PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); - while (hiddenEffect != null) { - // do not set source for hidden effects - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); - hiddenEffect = hiddenEffect.getHiddenPotionEffect(); - } - } - } - } else if (object instanceof ItemType itemType) { - for (PotionEffect potionEffect : PotionUtils.getPotionEffects(itemType)) { - potionEffects.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, itemType)); - } - } + potionEffects.addAll(PotionEffectProvider.of(object, this::error) + .getAll(state)); } return potionEffects.toArray(new SkriptPotionEffect[0]); } @@ -131,7 +80,7 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { return switch (mode) { case ADD, SET -> { if (state.includesHidden()) { - Skript.error("The hidden potion effects of an entity cannot be set or added to."); + Skript.error("Hidden potion effects cannot be directly set or added to."); yield null; } yield CollectionUtils.array(PotionEffect[].class); @@ -145,128 +94,50 @@ protected SkriptPotionEffect[] get(Event event, Object[] source) { public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { Object[] holders = getExpr().getArray(event); switch (mode) { - case SET, DELETE, RESET: + case ADD -> { + assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - reset(entity); - } else if (holder instanceof ItemType itemType) { - PotionUtils.clearPotionEffects(itemType); + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.add((PotionEffect) object); } } - if (mode != ChangeMode.SET) { // Fall through for SET to add effects - break; - } - //$FALL-THROUGH$ - case ADD: + } + case SET -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - entity.addPotionEffect((PotionEffect) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - PotionUtils.addPotionEffects(itemType, (PotionEffect) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + provider.clearAll(state); + for (Object object : delta) { + provider.add((PotionEffect) object); } } - break; - case REMOVE: + } + case REMOVE -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - remove(entity, (SkriptPotionEffect) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - remove(itemType, (SkriptPotionEffect) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.remove((SkriptPotionEffect) object, state); } } - break; - case REMOVE_ALL: + } + case REMOVE_ALL -> { assert delta != null; for (Object holder : holders) { - if (holder instanceof LivingEntity entity) { - for (Object object : delta) { - entity.removePotionEffect((PotionEffectType) object); - } - } else if (holder instanceof ItemType itemType) { - for (Object object : delta) { - PotionUtils.removePotionEffects(itemType, (PotionEffectType) object); - } + PotionEffectProvider provider = PotionEffectProvider.of(holder, this::error); + for (Object object : delta) { + provider.removeAll((PotionEffectType) object, state); } } - break; - default: - assert false; - } - } - - private void reset(LivingEntity entity) { - Collection potionEffects = entity.getActivePotionEffects(); - if (state == State.ACTIVE) { // preserve hidden effects - for (PotionEffect potionEffect : potionEffects) { - Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); - entity.removePotionEffect(potionEffect.getType()); - entity.addPotionEffects(hiddenEffects); - } - } else if (state == State.HIDDEN) { // preserve active effect - for (PotionEffect potionEffect : potionEffects) { - entity.removePotionEffect(potionEffect.getType()); - // applying a potion effect ignores the hidden effect value - entity.addPotionEffect(potionEffect); - } - } else { - for (PotionEffect potionEffect : potionEffects) { - entity.removePotionEffect(potionEffect.getType()); } - } - } - - private void remove(LivingEntity entity, SkriptPotionEffect potionEffect) { - PotionEffect entityEffect = entity.getPotionEffect(potionEffect.potionEffectType()); - if (entityEffect == null) { - return; - } - - Deque effects = PotionUtils.getHiddenEffects(entityEffect); - boolean madeChanges = false; - - // retain (some or all) hidden effects - // we only remove hidden effects if the user explicitly included them - if (state.includesHidden()) { - var effectsIterator = effects.iterator(); - while (effectsIterator.hasNext()) { - if (potionEffect.matchesQualities(effectsIterator.next())) { - effectsIterator.remove(); - madeChanges = true; + case DELETE, RESET -> { + for (Object holder : holders) { + PotionEffectProvider.of(holder, this::error) + .clearAll(state); } } } - - // retain the active effect - // unless the user is only removing hidden effects, we attempt to filter the active effect - if (state == State.HIDDEN || !potionEffect.matchesQualities(entityEffect)) { // preserve the effect - effects.addLast(entityEffect); - } else { - madeChanges = true; - } - - if (madeChanges) { // only remove and apply if changes were made - entity.removePotionEffect(entityEffect.getType()); - entity.addPotionEffects(effects); - } - } - - private void remove(ItemType itemType, SkriptPotionEffect potionEffect) { - for (PotionEffect itemEffect : PotionUtils.getPotionEffects(itemType)) { - if (potionEffect.matchesQualities(itemEffect)) { - PotionUtils.removePotionEffects(itemType, potionEffect.potionEffectType()); - break; // API doesn't support multiple effects of the same type - } - } } @Override @@ -285,7 +156,7 @@ public String toString(@Nullable Event event, boolean debug) { } @ApiStatus.Internal - public State getState() { + public RetrievalState getState() { return state; } diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java new file mode 100644 index 000000000..7fa73a8d8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/EntityProvider.java @@ -0,0 +1,200 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.potion.providers.EntityProvider.PotionAccessor; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class EntityProvider extends PotionEffectProvider { + + public EntityProvider(AreaEffectCloud areaEffectCloud) { + super(new AreaEffectCloudAccessor(areaEffectCloud)); + } + + public EntityProvider(Arrow arrow) { + super(new ArrowAccessor(arrow)); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + PotionType potionType = source.getBasePotionType(); + if (potionType != null) { + potionEffects.addAll(potionType.getPotionEffects()); + } + if (source.hasCustomEffects()) { + potionEffects.addAll(source.getCustomEffects()); + } + return potionEffects.stream() + .filter(effect -> { + for (PotionEffectType type : potionEffectTypes) { + if (type.equals(effect.getType())) { + return true; + } + } + return false; + }) + .map(effect -> SkriptPotionEffect.fromBukkitEffect(effect, this)) + .toList(); + } + + @Override + public Collection getAll(RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + PotionType potionType = source.getBasePotionType(); + if (potionType != null) { + for (PotionEffect effect : potionType.getPotionEffects()) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + } + } + if (source.hasCustomEffects()) { + for (PotionEffect effect : source.getCustomEffects()) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + } + } + return potionEffects; + } + + @Override + public void add(PotionEffect potionEffect) { + source.addCustomEffect(potionEffect, true); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + if (!state.includesActive() || !source.hasCustomEffects()) { + return; + } + for (PotionEffect itemEffect : source.getCustomEffects()) { + if (potionEffect.matchesQualities(itemEffect)) { + source.removeCustomEffect(potionEffect.potionEffectType()); + break; // API doesn't support multiple effects of the same type + } + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (!state.includesActive()) { + return; + } + source.removeCustomEffect(potionEffectType); + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return; + } + for (PotionEffectType potionEffectType : potionEffectTypes) { + source.removeCustomEffect(potionEffectType); + } + } + + @Override + public void clearAll(RetrievalState state) { + if (!state.includesActive()) { + return; + } + source.clearCustomEffects(); + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + source.removeCustomEffect(potionEffect.potionEffectType()); + runnable.run(); + source.addCustomEffect(potionEffect.asBukkitPotionEffect(), true); + } + + public interface PotionAccessor { + @Nullable PotionType getBasePotionType(); + boolean hasCustomEffects(); + List getCustomEffects(); + void addCustomEffect(PotionEffect effect, boolean overwrite); + void removeCustomEffect(PotionEffectType potionEffectType); + void clearCustomEffects(); + } + + private record AreaEffectCloudAccessor(AreaEffectCloud areaEffectCloud) implements PotionAccessor { + + @Override + public @Nullable PotionType getBasePotionType() { + return areaEffectCloud.getBasePotionType(); + } + + @Override + public boolean hasCustomEffects() { + return areaEffectCloud.hasCustomEffects(); + } + + @Override + public List getCustomEffects() { + return areaEffectCloud.getCustomEffects(); + } + + @Override + public void addCustomEffect(PotionEffect effect, boolean overwrite) { + areaEffectCloud.addCustomEffect(effect, overwrite); + } + + @Override + public void removeCustomEffect(PotionEffectType potionEffectType) { + areaEffectCloud.removeCustomEffect(potionEffectType); + } + + @Override + public void clearCustomEffects() { + areaEffectCloud.clearCustomEffects(); + } + + } + + private record ArrowAccessor(Arrow arrow) implements PotionAccessor { + + @Override + public @Nullable PotionType getBasePotionType() { + return arrow.getBasePotionType(); + } + + @Override + public boolean hasCustomEffects() { + return arrow.hasCustomEffects(); + } + + @Override + public List getCustomEffects() { + return arrow.getCustomEffects(); + } + + @Override + public void addCustomEffect(PotionEffect effect, boolean overwrite) { + arrow.addCustomEffect(effect, overwrite); + } + + @Override + public void removeCustomEffect(PotionEffectType potionEffectType) { + arrow.removeCustomEffect(potionEffectType); + } + + @Override + public void clearCustomEffects() { + arrow.clearCustomEffects(); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java new file mode 100644 index 000000000..3b5f3c01f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/ItemTypeProvider.java @@ -0,0 +1,173 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.aliases.ItemType; +import io.papermc.paper.potion.SuspiciousEffectEntry; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.inventory.meta.SuspiciousStewMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class ItemTypeProvider extends PotionEffectProvider { + + /** + * Attempts to retrieve a list of potion effects from an ItemType. + * @param itemType The ItemType to get potion effects from. + * @return A list of potion effects from an ItemType, if any were found. + */ + public static List getPotionEffects(ItemType itemType) { + List effects = new ArrayList<>(); + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + if (potionMeta.hasCustomEffects()) { + effects.addAll(potionMeta.getCustomEffects()); + } + if (potionMeta.hasBasePotionType()) { + //noinspection ConstantConditions - checked via hasBasePotionType + effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + effects.addAll(stewMeta.getCustomEffects()); + } + return effects; + } + + /** + * Adds potions effects to an ItemType. + * @param itemType The ItemType to modify. + * @param potionEffects The potion effects to add. + */ + public static void addPotionEffects(ItemType itemType, PotionEffect... potionEffects) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + for (PotionEffect potionEffect : potionEffects) { + potionMeta.addCustomEffect(potionEffect, true); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + for (PotionEffect potionEffect : potionEffects) { + stewMeta.addCustomEffect( + SuspiciousEffectEntry.create(potionEffect.getType(), potionEffect.getDuration()), true); + } + } + itemType.setItemMeta(meta); + } + + /** + * Removes potions effects from an ItemType. + * @param itemType The ItemType to modify. + * @param potionEffectTypes The potion effects to remove. + */ + public static void removePotionEffects(ItemType itemType, PotionEffectType... potionEffectTypes) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + potionMeta.removeCustomEffect(potionEffectType); + } + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + stewMeta.removeCustomEffect(potionEffectType); + } + } + itemType.setItemMeta(meta); + } + + /** + * Removes all potion effects from the ItemType's meta. + * @param itemType The ItemType to modify. + */ + public static void clearPotionEffects(ItemType itemType) { + ItemMeta meta = itemType.getItemMeta(); + if (meta instanceof PotionMeta potionMeta) { + potionMeta.clearCustomEffects(); + } else if (meta instanceof SuspiciousStewMeta stewMeta) { + stewMeta.clearCustomEffects(); + } + itemType.setItemMeta(meta); + } + + public ItemTypeProvider(ItemType source) { + super(source); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + List potionEffects = new ArrayList<>(); + for (PotionEffect effect : getPotionEffects(source)) { + for (PotionEffectType type : potionEffectTypes) { + if (type.equals(effect.getType())) { + potionEffects.add(SkriptPotionEffect.fromBukkitEffect(effect, this)); + break; + } + } + } + return potionEffects; + } + + @Override + public Collection getAll(RetrievalState state) { + if (!state.includesActive()) { + return List.of(); + } + return getPotionEffects(source).stream() + .map(potionEffect -> SkriptPotionEffect.fromBukkitEffect(potionEffect, this)) + .toList(); + } + + @Override + public void add(PotionEffect potionEffect) { + addPotionEffects(source, potionEffect); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + if (!state.includesActive()) { + return; + } + for (PotionEffect itemEffect : getPotionEffects(source)) { + if (potionEffect.matchesQualities(itemEffect)) { + removePotionEffects(source, potionEffect.potionEffectType()); + break; // API doesn't support multiple effects of the same type + } + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (!state.includesActive()) { + return; + } + removePotionEffects(source, potionEffectType); + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (!state.includesActive()) { + return; + } + removePotionEffects(source, potionEffectTypes); + } + + @Override + public void clearAll(RetrievalState state) { + if (!state.includesActive()) { + return; + } + clearPotionEffects(source); + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + removePotionEffects(source, potionEffect.potionEffectType()); + runnable.run(); + addPotionEffects(source, potionEffect.asBukkitPotionEffect()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java new file mode 100644 index 000000000..39065f7e4 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/LivingEntityProvider.java @@ -0,0 +1,238 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.util.Timespan; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionDuration; +import org.skriptlang.skript.bukkit.potion.util.PotionUtils; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.List; + +class LivingEntityProvider extends PotionEffectProvider { + + public LivingEntityProvider(LivingEntity source) { + super(source); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + List potionEffects = new ArrayList<>(); + for (PotionEffectType type : potionEffectTypes) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect != null) { + getEffects(potionEffect, state, potionEffects); + } + } + return potionEffects; + } + + @Override + public Collection getAll(RetrievalState state) { + List potionEffects = new ArrayList<>(); + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + getEffects(potionEffect, state, potionEffects); + } + return potionEffects; + } + + private void getEffects(PotionEffect potionEffect, RetrievalState state, Collection destination) { + if (state.includesActive()) { + destination.add(SkriptPotionEffect.fromBukkitEffect(potionEffect, this)); + } + if (state.includesHidden()) { + PotionEffect hiddenEffect = potionEffect.getHiddenPotionEffect(); + while (hiddenEffect != null) { + // do not set source for hidden effects + destination.add(SkriptPotionEffect.fromBukkitEffect(hiddenEffect)); + hiddenEffect = hiddenEffect.getHiddenPotionEffect(); + } + } + } + + @Override + public void add(PotionEffect potionEffect) { + source.addPotionEffect(potionEffect); + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + PotionEffect entityEffect = source.getPotionEffect(potionEffect.potionEffectType()); + if (entityEffect == null) { + return; + } + + Deque effects = PotionUtils.getHiddenEffects(entityEffect); + boolean madeChanges = false; + + // retain (some or all) hidden effects + // we only remove hidden effects if the user explicitly included them + if (state.includesHidden()) { + var effectsIterator = effects.iterator(); + while (effectsIterator.hasNext()) { + if (potionEffect.matchesQualities(effectsIterator.next())) { + effectsIterator.remove(); + madeChanges = true; + } + } + } + + // retain the active effect + // unless the user is only removing hidden effects, we attempt to filter the active effect + if (state == RetrievalState.HIDDEN || !potionEffect.matchesQualities(entityEffect)) { // preserve the effect + effects.addLast(entityEffect); + } else { + madeChanges = true; + } + + if (madeChanges) { // only remove and apply if changes were made + source.removePotionEffect(entityEffect.getType()); + source.addPotionEffects(effects); + } + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + if (state == RetrievalState.ACTIVE || state == RetrievalState.HIDDEN) { + clear(new PotionEffectType[]{potionEffectType}, state); + } else { + source.removePotionEffect(potionEffectType); + } + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + if (state == RetrievalState.ACTIVE) { // preserve hidden effects + for (PotionEffectType type : potionEffectTypes) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect == null) { + continue; + } + Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); + source.removePotionEffect(type); + source.addPotionEffects(hiddenEffects); + } + } else if (state == RetrievalState.HIDDEN) { // preserve active effect + for (PotionEffectType type : potionEffectTypes) { + PotionEffect original = source.getPotionEffect(type); + source.removePotionEffect(type); + if (original != null) { + // applying a potion effect ignores the hidden effect value + source.addPotionEffect(original); + } + } + } else { + for (PotionEffectType type : potionEffectTypes) { + source.removePotionEffect(type); + } + } + } + + @Override + public void clearAll(RetrievalState state) { + if (state == RetrievalState.ACTIVE) { // preserve hidden effects + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + Deque hiddenEffects = PotionUtils.getHiddenEffects(potionEffect); + source.removePotionEffect(potionEffect.getType()); + source.addPotionEffects(hiddenEffects); + } + } else if (state == RetrievalState.HIDDEN) { // preserve active effect + for (PotionEffect potionEffect : source.getActivePotionEffects()) { + source.removePotionEffect(potionEffect.getType()); + // applying a potion effect ignores the hidden effect value + source.addPotionEffect(potionEffect); + } + } else { + source.clearActivePotionEffects(); + } + } + + @Override + public void modify(PotionEffectType[] types, RetrievalState state, Object[] delta, ChangeMode mode) { + for (PotionEffectType type : types) { + PotionEffect potionEffect = source.getPotionEffect(type); + if (potionEffect == null) { + continue; + } + + Deque finalEffects; // effects to be applied + Deque effects; // effects to be filtered + boolean madeChanges = false; + + if (state.includesHidden()) { // modify hidden effects + finalEffects = new ArrayDeque<>(); + effects = PotionUtils.getHiddenEffects(potionEffect); + } else { // otherwise, simply preserve the hidden effects + finalEffects = PotionUtils.getHiddenEffects(potionEffect); + effects = new ArrayDeque<>(); + } + + if (state.includesActive()) { // need to modify the active effect too + effects.addLast(potionEffect); + } + + // filter effects + effectLoop: for (PotionEffect effect : effects) { + SkriptPotionEffect skriptEffect = SkriptPotionEffect.fromBukkitEffect(effect); + for (Object object : delta) { + if (object instanceof Timespan timespan) { + ExprPotionDuration.changeSafe(skriptEffect, timespan, mode); + madeChanges = true; + } else if (object instanceof SkriptPotionEffect base) { + if (base.matchesQualities(effect)) { // remove this effect + madeChanges = true; + continue effectLoop; + } + } + } + // since we iterate most to least hidden, we need to preserve that order + finalEffects.addLast(skriptEffect.asBukkitPotionEffect()); + } + if (!madeChanges) { // no potion effects were modified, don't reapply effects + return; + } + + if (!state.includesActive()) { // if we didn't modify the active effect, we need to push it now + effects.addLast(potionEffect); + } + + source.removePotionEffect(type); + source.addPotionEffects(finalEffects); + } + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + Deque hiddenEffects = null; + PotionEffectType potionEffectType = potionEffect.potionEffectType(); + if (source.hasPotionEffect(potionEffectType)) { + //noinspection DataFlowIssue - NotNull by hasPotionEffect check + hiddenEffects = PotionUtils.getHiddenEffects(source.getPotionEffect(potionEffectType)); + source.removePotionEffect(potionEffectType); + } + runnable.run(); + PotionEffect updatedPotionEffect = potionEffect.asBukkitPotionEffect(); + if (hiddenEffects != null) { // reapply hidden effects + for (PotionEffect hiddenEffect : hiddenEffects) { + // we need to add this potion effect in the right order + // it might end up not being applied at all, but we'll let the game determine that + if (updatedPotionEffect != null && + (hiddenEffect.isShorterThan(updatedPotionEffect) || hiddenEffect.getAmplifier() > updatedPotionEffect.getAmplifier())) { + source.addPotionEffect(updatedPotionEffect); + updatedPotionEffect = null; + } + source.addPotionEffect(hiddenEffect); + } + } + if (updatedPotionEffect != null) { + source.addPotionEffect(updatedPotionEffect); + } + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java new file mode 100644 index 000000000..b9ae38ce5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/NullProvider.java @@ -0,0 +1,56 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.Collection; +import java.util.List; + +public class NullProvider extends PotionEffectProvider { + + public NullProvider() { + super(null); + } + + @Override + public Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state) { + return List.of(); + } + + @Override + public Collection getAll(RetrievalState state) { + return List.of(); + } + + @Override + public void add(PotionEffect potionEffect) { + + } + + @Override + public void remove(SkriptPotionEffect potionEffect, RetrievalState state) { + + } + + @Override + public void removeAll(PotionEffectType potionEffectType, RetrievalState state) { + + } + + @Override + public void clear(PotionEffectType[] potionEffectTypes, RetrievalState state) { + + } + + @Override + public void clearAll(RetrievalState state) { + + } + + @Override + public void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable) { + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java new file mode 100644 index 000000000..633c80451 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/providers/PotionEffectProvider.java @@ -0,0 +1,200 @@ +package org.skriptlang.skript.bukkit.potion.providers; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Utils; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.ApiStatus; +import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionDuration; +import org.skriptlang.skript.bukkit.potion.util.SkriptPotionEffect; + +import java.util.Collection; +import java.util.function.Consumer; + +/** + * A wrapper for working with objects that provide potion effects. + * @param The type providing potion effects. + * @see #of(Object, Consumer) + */ +@ApiStatus.Internal +public abstract class PotionEffectProvider { + + /** + * @param object The object to obtain a provider from. + * @param errorProducer A consumer to use for printing errors. + * @return A provider wrapping {@code object}. + * If {@code object} is not a known potion provider, {@code errorProducer} is invoked + * and a dummy provider is returned. + */ + public static PotionEffectProvider of(Object object, Consumer errorProducer) { + return switch (object) { + case AreaEffectCloud areaEffectCloud -> new EntityProvider(areaEffectCloud); + case Arrow arrow -> new EntityProvider(arrow); + case ItemType itemType -> new ItemTypeProvider(itemType); + case LivingEntity livingEntity -> new LivingEntityProvider(livingEntity); + default -> { + errorProducer.accept(Utils.A(Classes.toString(object)) + " does not have potion effects"); + yield new NullProvider(); + } + }; + } + + /** + * Describes the type of potion effects being retrieved in a retrieval operation. + * This is used for distinguishing between active and hidden potion effects, which only apply to {@link LivingEntity}. + */ + public enum RetrievalState { + + /** + * Unspecified retrieval state. + * Behavior will differ depending on retrieval context. + */ + UNSET, + + /** + * Obtaining only active effects + */ + ACTIVE, + + /** + * Obtaining only hidden effects + */ + HIDDEN, + + /** + * Obtaining both active and hidden effects. + * A combination of {@link #ACTIVE} and {@link #HIDDEN}. + */ + BOTH; + + /** + * Utility method for standardized mapping of a parse tag to a retrieval state. + * @param tag Parse tag representing a retrieval state. + * @return A retrieval state. + */ + public static RetrievalState fromParseTag(String tag) { + return switch (tag) { + case "active" -> ACTIVE; // explicitly active + case "hidden" -> HIDDEN; // explicitly hidden + case "both" -> BOTH; // explicitly active and hidden + default -> UNSET; // implicitly active for get, implicitly active and hidden for delete/reset + }; + } + + /** + * @return Whether this state includes active potion effects. + */ + public boolean includesActive() { + return this != RetrievalState.HIDDEN; + } + + /** + * @return Whether this state includes hidden potion effects. + */ + public boolean includesHidden() { + return this == RetrievalState.HIDDEN || this == RetrievalState.BOTH; + } + + /** + * @return A user-friendly string describing this retrieval state. + */ + public String displayName() { + return switch (this) { + case UNSET -> ""; + case ACTIVE -> "active"; + case HIDDEN -> "hidden"; + case BOTH -> "active and hidden"; + }; + } + } + + /** + * Source object providing potion effects. + */ + protected final T source; + + /** + * @param source The source object providing potion effects. + */ + public PotionEffectProvider(T source) { + this.source = source; + } + + /** + * Obtains specific types of potion effects from this provider. + * @param potionEffectTypes The type of potion effects to obtain. + * @param state The type of retrieval to perform. + * @return All potion effects of {@code potionEffectTypes} present on this provider. + */ + public abstract Collection get(PotionEffectType[] potionEffectTypes, RetrievalState state); + + /** + * Obtains all potion effects from this provider. + * @param state The type of retrieval to perform. + * @return All potion effects present on this provider. + */ + public abstract Collection getAll(RetrievalState state); + + /** + * Adds a potion effect to this provider. + * @param potionEffect The potion effect to add. + */ + public abstract void add(PotionEffect potionEffect); + + /** + * Removes a potion effect from this provider. + * A potion effect only needs to match the qualities of {@code potionEffect} to be removed. + * Thus, an effect will be removed even if it has other, distinguishing qualities. + * @param potionEffect The potion effect to remove. + * @param state State determining whether certain types of potion effects should be ignored. + */ + public abstract void remove(SkriptPotionEffect potionEffect, RetrievalState state); + + /** + * Removes all potion effects of a specific type from this provider. + * @param potionEffectType The type of potion effect to remove. + * @param state State determining whether certain types of potion effects should be preserved. + */ + public abstract void removeAll(PotionEffectType potionEffectType, RetrievalState state); + + /** + * Clears all potion effects of {@code potionEffectTypes} from this provider. + * @param potionEffectTypes The types of potion effects to clear. + * @param state State determining whether certain types of potion effects should be ignored. + */ + public abstract void clear(PotionEffectType[] potionEffectTypes, RetrievalState state); + + /** + * Clears all potion effects from this provider. + * @param state State determining whether certain types of potion effects should be preserved. + */ + public abstract void clearAll(RetrievalState state); + + /** + * Modifies properties of existing potion effects on this provider. + * @param types The types of potion effects to modify. + * @param state State determining whether certain types of potion effects should be ignored. + * @param delta The change values. + * @param mode The type of modification to perform. + */ + public void modify(PotionEffectType[] types, RetrievalState state, Object[] delta, ChangeMode mode) { + if (delta[0] instanceof Timespan timespan) { + get(types, state).forEach(potionEffect -> + ExprPotionDuration.changeSafe(potionEffect, timespan, mode)); + } + } + + /** + * Used for mirroring modifications of a potion effect actively applied to this provider. + * @param potionEffect The potion effect being modified. + * @param runnable A runnable that applies the modification(s) to {@code potionEffect} when invoked. + */ + public abstract void mirrorEffectChanges(SkriptPotionEffect potionEffect, Runnable runnable); + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java b/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java index e6fc18b59..0a4b43015 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/util/PotionUtils.java @@ -1,21 +1,11 @@ package org.skriptlang.skript.bukkit.potion.util; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Timespan.TimePeriod; -import io.papermc.paper.potion.SuspiciousEffectEntry; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.inventory.meta.SuspiciousStewMeta; import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; -import java.util.List; public final class PotionUtils { @@ -29,81 +19,6 @@ public final class PotionUtils { */ public static final String DEFAULT_DURATION_STRING = new Timespan(TimePeriod.TICK, DEFAULT_DURATION_TICKS).toString(); - /** - * Attempts to retrieve a list of potion effects from an ItemType. - * @param itemType The ItemType to get potion effects from. - * @return A list of potion effects from an ItemType, if any were found. - */ - public static List getPotionEffects(ItemType itemType) { - List effects = new ArrayList<>(); - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - if (potionMeta.hasCustomEffects()) { - effects.addAll(potionMeta.getCustomEffects()); - } - if (potionMeta.hasBasePotionType()) { - //noinspection ConstantConditions - checked via hasBasePotionType - effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - effects.addAll(stewMeta.getCustomEffects()); - } - return effects; - } - - /** - * Adds potions effects to an ItemType. - * @param itemType The ItemType to modify. - * @param potionEffects The potion effects to add. - */ - public static void addPotionEffects(ItemType itemType, PotionEffect... potionEffects) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - for (PotionEffect potionEffect : potionEffects) { - potionMeta.addCustomEffect(potionEffect, true); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - for (PotionEffect potionEffect : potionEffects) { - stewMeta.addCustomEffect( - SuspiciousEffectEntry.create(potionEffect.getType(), potionEffect.getDuration()), true); - } - } - itemType.setItemMeta(meta); - } - - /** - * Removes potions effects from an ItemType. - * @param itemType The ItemType to modify. - * @param potionEffectTypes The potion effects to remove. - */ - public static void removePotionEffects(ItemType itemType, PotionEffectType... potionEffectTypes) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - for (PotionEffectType potionEffectType : potionEffectTypes) { - potionMeta.removeCustomEffect(potionEffectType); - } - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - for (PotionEffectType potionEffectType : potionEffectTypes) { - stewMeta.removeCustomEffect(potionEffectType); - } - } - itemType.setItemMeta(meta); - } - - /** - * Removes all potion effects from the ItemType's meta. - * @param itemType The ItemType to modify. - */ - public static void clearPotionEffects(ItemType itemType) { - ItemMeta meta = itemType.getItemMeta(); - if (meta instanceof PotionMeta potionMeta) { - potionMeta.clearCustomEffects(); - } else if (meta instanceof SuspiciousStewMeta stewMeta) { - stewMeta.clearCustomEffects(); - } - itemType.setItemMeta(meta); - } - /** * A utility method to obtain the hidden effects of a potion effect. * @param effect The effect to obtain hidden effects from. diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java b/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java index b884e2121..93ef84eec 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/util/SkriptPotionEffect.java @@ -1,14 +1,12 @@ package org.skriptlang.skript.bukkit.potion.util; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; import ch.njol.skript.lang.Expression; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Timespan.TimePeriod; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import org.bukkit.entity.LivingEntity; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.ApiStatus; @@ -17,6 +15,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffect; import org.skriptlang.skript.bukkit.potion.elements.expressions.ExprPotionEffects; +import org.skriptlang.skript.bukkit.potion.providers.PotionEffectProvider; import java.io.StreamCorruptedException; import java.util.Deque; @@ -40,11 +39,10 @@ public class SkriptPotionEffect implements Cloneable, YggdrasilExtendedSerializa private @Nullable PotionEffect lastEffect; /** - * Sources for where this effect was created from. + * A source for where this effect was created from. * Modifying this effect will update the effect on any sources. */ - private @Nullable LivingEntity entitySource; - private @Nullable ItemType itemSource; + private @Nullable PotionEffectProvider sourceProvider; /** * Internal usage only for serialization. @@ -84,28 +82,13 @@ public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect) { * source is expected to currently be affected by potionEffect. * When changes are made to this potion effect, they will be reflected on source. * @param potionEffect The potion effect to obtain properties from. - * @param source An entity that should mirror the changes to this potion effect. + * @param source A potion effect provider that should mirror the changes to this potion effect. * @return A potion effect whose properties are set from potionEffect. * @see #fromBukkitEffect(PotionEffect) */ - public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, LivingEntity source) { + public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, PotionEffectProvider source) { SkriptPotionEffect skriptPotionEffect = fromBukkitEffect(potionEffect); - skriptPotionEffect.entitySource = source; - return skriptPotionEffect; - } - - /** - * Constructs a SkriptPotionEffect from a Bukkit PotionEffect and source item. - * source is expected to be an item (potion, stew, etc.) whose meta contains potionEffect. - * When changes are made to this potion effect, they will be reflected on source. - * @param potionEffect The potion effect to obtain properties from. - * @param source An item that should mirror the changes to this potion effect. - * @return A potion effect whose properties are set from potionEffect. - * @see #fromBukkitEffect(PotionEffect) - */ - public static SkriptPotionEffect fromBukkitEffect(PotionEffect potionEffect, ItemType source) { - SkriptPotionEffect skriptPotionEffect = fromBukkitEffect(potionEffect); - skriptPotionEffect.itemSource = source; + skriptPotionEffect.sourceProvider = source; return skriptPotionEffect; } @@ -348,34 +331,10 @@ public boolean matchesQualities(PotionEffect potionEffect) { */ private void withSource(Runnable runnable) { - Deque hiddenEffects = null; - if (entitySource != null && entitySource.hasPotionEffect(potionEffectType)) { - //noinspection DataFlowIssue - NotNull by hasPotionEffect check - hiddenEffects = PotionUtils.getHiddenEffects(entitySource.getPotionEffect(potionEffectType)); - entitySource.removePotionEffect(potionEffectType); - } else if (itemSource != null) { - PotionUtils.removePotionEffects(itemSource, potionEffectType); - } - runnable.run(); - if (entitySource != null) { - PotionEffect thisPotionEffect = asBukkitPotionEffect(); - if (hiddenEffects != null) { // reapply hidden effects - for (PotionEffect hiddenEffect : hiddenEffects) { - // we need to add this potion effect in the right order - // it might end up not being applied at all, but we'll let the game determine that - if (thisPotionEffect != null && - (hiddenEffect.isShorterThan(thisPotionEffect) || hiddenEffect.getAmplifier() > thisPotionEffect.getAmplifier())) { - entitySource.addPotionEffect(thisPotionEffect); - thisPotionEffect = null; - } - entitySource.addPotionEffect(hiddenEffect); - } - } - if (thisPotionEffect != null) { - entitySource.addPotionEffect(asBukkitPotionEffect()); - } - } else if (itemSource != null) { - PotionUtils.addPotionEffects(itemSource, asBukkitPotionEffect()); + if (sourceProvider == null) { + runnable.run(); + } else { + sourceProvider.mirrorEffectChanges(this, runnable); } } @@ -411,8 +370,7 @@ public SkriptPotionEffect clone() { SkriptPotionEffect skriptPotionEffect = (SkriptPotionEffect) super.clone(); // we do not copy over sources on clones // for example, copying a potion effect into a variable should not continue tracking the source - skriptPotionEffect.entitySource = null; - skriptPotionEffect.itemSource = null; + skriptPotionEffect.sourceProvider = null; return skriptPotionEffect; } catch (CloneNotSupportedException e) { throw new AssertionError(); diff --git a/src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfos.java b/src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfos.java index a92d2b07a..c27afcec2 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfos.java +++ b/src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfos.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.SequencedCollection; +import java.util.function.Supplier; /** * A class containing the interfaces representing Bukkit-specific SyntaxInfo implementations. @@ -32,7 +33,26 @@ public interface Event extends SyntaxInfo { Key> KEY = Key.of("event"); /** - * @param eventClass The Structure class the info will represent. + * Constructs a simple {@link SkriptEvent} syntax info for a class from event information and patterns. + * @param eventClass The SkriptEvent class the info will represent. + * @param instanceSupplier A supplier for creating new instances of {@code type}. + * @param name The name of the SkriptEvent. + * @param bukkitEventClass The Bukkit event that should trigger the SkriptEvent. + * @param patterns Patterns describing the syntax. + * @return A syntax info representing {@code type}. + */ + @Contract("_, _, _, _, _ -> new") + static Event simple(Class eventClass, Supplier instanceSupplier, + String name, Class bukkitEventClass, String... patterns) { + return builder(eventClass, name) + .supplier(instanceSupplier) + .addEvent(bukkitEventClass) + .addPatterns(patterns) + .build(); + } + + /** + * @param eventClass The SkriptEvent class the info will represent. * @param name The name of the SkriptEvent. * @return A Structure-specific builder for creating a syntax info representing type. */ @@ -166,7 +186,7 @@ interface Builder, E extends SkriptEvent> extends Syntax * @return This builder. * @see Event#since() */ - @Contract("_ -> this") + @Contract("-> this") B clearSince(); /** diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/TextModule.java b/src/main/java/org/skriptlang/skript/bukkit/text/TextModule.java index ec67d0668..b730c65d5 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/text/TextModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/text/TextModule.java @@ -64,6 +64,7 @@ public void loadSelf(SkriptAddon addon) { EffSendTitle::register, ExprColored::register, ExprRawString::register, + ExprResolvedComponent::register, ExprStringColor::register ); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/elements/effects/EffSendTitle.java b/src/main/java/org/skriptlang/skript/bukkit/text/elements/effects/EffSendTitle.java index f33f7f880..f48ee29cb 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/text/elements/effects/EffSendTitle.java +++ b/src/main/java/org/skriptlang/skript/bukkit/text/elements/effects/EffSendTitle.java @@ -108,7 +108,7 @@ protected void execute(Event event) { if (stayTimespan == null) { return; } - stay = Duration.from(stayTimespan); + stay = stayTimespan.getDuration(); specifiesTimes = true; } Duration fadeIn; @@ -119,7 +119,7 @@ protected void execute(Event event) { if (fadeInTimespan == null) { return; } - fadeIn = Duration.from(fadeInTimespan); + fadeIn = fadeInTimespan.getDuration(); specifiesTimes = true; } Duration fadeOut; @@ -130,7 +130,7 @@ protected void execute(Event event) { if (fadeOutTimespan == null) { return; } - fadeOut = Duration.from(fadeOutTimespan); + fadeOut = fadeOutTimespan.getDuration(); specifiesTimes = true; } diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprColored.java b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprColored.java index 8dbdc9db1..139e7617d 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprColored.java +++ b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprColored.java @@ -38,6 +38,7 @@ public class ExprColored extends SimplePropertyExpression { public static void register(SyntaxRegistry syntaxRegistry) { syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprColored.class, Object.class) .supplier(ExprColored::new) + .priority(DEFAULT_PRIORITY) .addPatterns("[negated:(un|non)[-]](colo[u]r-|colo[u]red )%strings%", "[negated:(un|non)[-]](format-|formatted )%strings%") .build()); diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprResolvedComponent.java b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprResolvedComponent.java new file mode 100644 index 000000000..86733ffcf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprResolvedComponent.java @@ -0,0 +1,99 @@ +package org.skriptlang.skript.bukkit.text.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import io.papermc.paper.text.PaperComponents; +import net.kyori.adventure.text.Component; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Name("Resolve Text Component") +@Description(""" + Resolves a component with the context of a command sender or other entity. + Certain kinds of components, such as NBT components, Score components, and Selector components, are required to be resolved to display a proper output. + Note that the option to bypass required permissions only applies for players. + """) +@Example(""" + send formatted "Hello , I'm !" resolved for player + """) +@Since("2.16") +public class ExprResolvedComponent extends SimpleExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprResolvedComponent.class, Component.class) + .supplier(ExprResolvedComponent::new) + .addPattern("%textcomponents% resolved for %commandsender% [bypass:(bypassing|ignoring) required permissions]") + .build()); + } + + private Expression components; + private Expression contextProvider; + private boolean bypassPermissions; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + this.components = (Expression) expressions[0]; + //noinspection unchecked + this.contextProvider = (Expression) expressions[1]; + this.bypassPermissions = parseResult.hasTag("bypass"); + return true; + } + + @Override + protected Component @Nullable [] get(Event event) { + CommandSender contextProvider = this.contextProvider.getSingle(event); + if (contextProvider == null) { + return new Component[0]; + } + Entity scoreboardSubject = contextProvider instanceof Entity entity ? entity : null; + // If the context provider is not a player, say a Pig, don't require it to have permissions + boolean bypassPermissions = this.bypassPermissions || !(contextProvider instanceof OfflinePlayer); + List components = new ArrayList<>(); + for (Component component : this.components.getArray(event)) { + try { + components.add(PaperComponents.resolveWithContext(component, contextProvider, scoreboardSubject, bypassPermissions)); + } catch (IOException | RuntimeException ignored) { + // IOException is thrown for lack of permission + // RuntimeException is thrown for something like an invalid selector + components.add(component); + } + } + return components.toArray(Component[]::new); + } + + @Override + public boolean isSingle() { + return components.isSingle(); + } + + @Override + public Class getReturnType() { + return Component.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append(components, "resolved for", contextProvider) + .appendIf(bypassPermissions, "bypassing required permissions") + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/types/InventoryClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/types/InventoryClassInfo.java index 403728b35..82d4003f3 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/types/InventoryClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/types/InventoryClassInfo.java @@ -64,6 +64,10 @@ public InventoryClassInfo() { "The name of the inventory. Can be set or reset.", Skript.instance(), new InventoryNameHandler()) + .property(Property.VIEWERS, + "The viewers of the inventory.", + Skript.instance(), + new InventoryViewersHandler()) .property(Property.IS_EMPTY, "Whether the inventory contains no items (all slots contain air).", Skript.instance(), @@ -74,6 +78,7 @@ public InventoryClassInfo() { } return true; })); + } private static class InventoryParser extends Parser { @@ -293,4 +298,22 @@ public void change(Inventory inventory, Object @Nullable [] delta, ChangeMode mo // } + private static class InventoryViewersHandler implements ExpressionPropertyHandler { + // + + @Override + public Object convert(Inventory inventory) { + return inventory.getViewers().stream() + .filter(viewer -> viewer instanceof Player) + .toArray(Player[]::new); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public @NotNull Class returnType() { + return (Class) Player.class; + } + // + } + } diff --git a/src/main/java/org/skriptlang/skript/bukkit/types/ItemTypeClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/types/ItemTypeClassInfo.java index 85c6becc8..0f97d964b 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/types/ItemTypeClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/types/ItemTypeClassInfo.java @@ -10,9 +10,12 @@ import ch.njol.skript.classes.YggdrasilSerializer; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.util.EnchantmentType; +import ch.njol.util.coll.CollectionUtils; import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -47,6 +50,10 @@ public ItemTypeClassInfo() { .parser(new ItemTypeParser()) .cloner(ItemType::clone) .serializer(new YggdrasilSerializer<>()) + .property(Property.TITLE, + "An item's title. This currently only applies to signed books.", + Skript.instance(), + new ItemTypeTitleHandler()) .property(Property.NAME, "An item type's custom name, if set. Can be set or reset.", Skript.instance(), @@ -103,6 +110,40 @@ public String toVariableNameString(ItemType itemType) { // } + private static class ItemTypeTitleHandler implements ExpressionPropertyHandler { + // + @Override + public @Nullable Component convert(ItemType item) { + if (item.getItemMeta() instanceof BookMeta bookMeta && bookMeta.hasTitle()) { + return bookMeta.title(); + } + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(Component.class); + default -> null; + }; + } + + @Override + public void change(ItemType item, Object @Nullable [] delta, ChangeMode mode) { + Component title = delta == null ? null : (Component) delta[0]; + if (item.getItemMeta() instanceof BookMeta bookMeta) { + bookMeta.title(title); + item.setItemMeta(bookMeta); + } + } + + @Override + public @NotNull Class returnType() { + return Component.class; + } + // + } + private static class ItemTypeNameHandler implements ExpressionPropertyHandler { // @Override diff --git a/src/main/java/org/skriptlang/skript/bukkit/types/LocationClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/types/LocationClassInfo.java index c971f8d22..4462e96e9 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/types/LocationClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/types/LocationClassInfo.java @@ -5,15 +5,24 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; +import ch.njol.skript.command.Commands; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.util.BlockUtils; import ch.njol.yggdrasil.Fields; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.DoubleChest; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; @@ -42,6 +51,26 @@ public LocationClassInfo() { Skript.instance(), new LocationWXYZHandler()); + Converters.registerConverter(OfflinePlayer.class, Location.class, OfflinePlayer::getLocation); + Converters.registerConverter(Block.class, Location.class, BlockUtils::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + Converters.registerConverter(Entity.class, Location.class, Entity::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + Converters.registerConverter(InventoryHolder.class, Location.class, holder -> { + if (holder instanceof Entity entity) + return entity.getLocation(); + if (holder instanceof Block block) + return block.getLocation(); + if (holder instanceof BlockState state) + return BlockUtils.getLocation(state.getBlock()); + if (holder instanceof DoubleChest doubleChest) { + if (doubleChest.getLeftSide() != null) { + return BlockUtils.getLocation(((BlockState) doubleChest.getLeftSide()).getBlock()); + } else if (doubleChest.getRightSide() != null) { + return BlockUtils.getLocation(((BlockState) doubleChest.getRightSide()).getBlock()); + } + } + return null; + }); + } private static class LocationWXYZHandler extends WXYZHandler { diff --git a/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/WorldBorderModule.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/WorldBorderModule.java new file mode 100644 index 000000000..d52ba3da1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/WorldBorderModule.java @@ -0,0 +1,172 @@ +package org.skriptlang.skript.bukkit.worldborder.elements; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.util.SimpleEvent; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Timespan; +import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent; +import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent; +import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent; +import org.bukkit.Location; +import org.bukkit.WorldBorder; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.HierarchicalAddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; +import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.bukkit.worldborder.elements.effects.EffWorldBorderExpand; +import org.skriptlang.skript.bukkit.worldborder.elements.expressions.*; +import org.skriptlang.skript.registration.SyntaxRegistry; + +public class WorldBorderModule extends HierarchicalAddonModule { + + public WorldBorderModule(AddonModule parentModule) { + super(parentModule); + } + + @Override + protected void initSelf(SkriptAddon addon) { + Classes.registerClass(new ClassInfo<>(WorldBorder.class, "worldborder") + .user("world ?borders?") + .name("World Border") + .description("Represents the border of a world or player.") + .since("2.11") + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(WorldBorder border, int flags) { + if (border.getWorld() == null) + return "virtual world border"; + return "world border of world named '" + border.getWorld().getName() + "'"; + } + + @Override + public String toVariableNameString(WorldBorder border) { + return toString(border, 0); + } + }) + .defaultExpression(new EventValueExpression<>(WorldBorder.class))); + } + + @Override + protected void loadSelf(SkriptAddon addon) { + EventValueRegistry eventValueRegistry = addon.registry(EventValueRegistry.class); + SyntaxRegistry syntaxRegistry = moduleRegistry(addon); + + syntaxRegistry.register( + BukkitSyntaxInfos.Event.KEY, + BukkitSyntaxInfos.Event.builder(SimpleEvent.class, "World Border Bounds Change") + .addEvent(WorldBorderBoundsChangeEvent.class) + .addPatterns("world[ ]border [bounds] chang(e|ing)") + .addDescription( + "Called when a world border changes its bounds, either over time, or instantly.", + "This event does not get called for virtual borders." + ) + .addExample(""" + on worldborder bounds change: + broadcast "The diameter of %event-worldborder% is changing from %past event-number% to %event-number% over the next %event-timespan% + """) + .addSince("2.11") + .build() + ); + + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeEvent.class, WorldBorder.class) + .getter(WorldBorderBoundsChangeEvent::getWorldBorder) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeEvent.class, Number.class) + .getter(WorldBorderBoundsChangeEvent::getNewSize) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeEvent.class, Timespan.class) + .getter(event -> new Timespan(event.getDurationTicks())) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeEvent.class, Number.class) + .getter(WorldBorderBoundsChangeEvent::getOldSize) + .time(EventValue.Time.PAST) + .build()); + + syntaxRegistry.register( + BukkitSyntaxInfos.Event.KEY, + BukkitSyntaxInfos.Event.builder(SimpleEvent.class, "World Border Bounds Finish Change") + .addEvent(WorldBorderBoundsChangeFinishEvent.class) + .addPatterns("world[ ]border [bounds] finish chang(e|ing)") + .addDescription(""" + Called when a moving world border has finished its move. + This event does not get called for virtual borders. + """) + .addExample(""" + on worldborder bounds finish change: + broadcast "Over the past %event-timespan%, the diameter of %event-worldborder% went from %past event-number% to %event-number%" + """) + .addSince("2.11") + .build() + ); + + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeFinishEvent.class, WorldBorder.class) + .getter(WorldBorderBoundsChangeFinishEvent::getWorldBorder) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeFinishEvent.class, Number.class) + .getter(WorldBorderBoundsChangeFinishEvent::getNewSize) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeFinishEvent.class, Number.class) + .getter(WorldBorderBoundsChangeFinishEvent::getOldSize) + .time(EventValue.Time.PAST) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderBoundsChangeFinishEvent.class, Timespan.class) + .getter(event -> new Timespan((long) event.getDuration())) + .build()); + + syntaxRegistry.register( + BukkitSyntaxInfos.Event.KEY, + BukkitSyntaxInfos.Event.builder(SimpleEvent.class, "World Border Center Change") + .addEvent(WorldBorderCenterChangeEvent.class) + .addPatterns("world[ ]border center chang(e|ing)") + .addDescription(""" + Called when a world border's center has changed. + This event does not get called for virtual borders. + """) + .addExample(""" + on worldborder center change: + broadcast "The center of %event-worldborder% has moved from %past event-location% to %event-location% + """) + .addSince("2.11") + .build() + ); + + eventValueRegistry.register(EventValue.builder(WorldBorderCenterChangeEvent.class, WorldBorder.class) + .getter(WorldBorderCenterChangeEvent::getWorldBorder) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderCenterChangeEvent.class, Location.class) + .getter(WorldBorderCenterChangeEvent::getNewCenter) + .build()); + eventValueRegistry.register(EventValue.builder(WorldBorderCenterChangeEvent.class, Location.class) + .getter(WorldBorderCenterChangeEvent::getOldCenter) + .time(EventValue.Time.PAST) + .build()); + + register(addon, + a -> ExprSecCreateWorldBorder.register(a, eventValueRegistry), + EffWorldBorderExpand::register, + ExprWorldBorder::register, + ExprWorldBorderCenter::register, + ExprWorldBorderDamageAmount::register, + ExprWorldBorderDamageBuffer::register, + ExprWorldBorderSize::register, + ExprWorldBorderWarningDistance::register, + ExprWorldBorderWarningTime::register + ); + } + + @Override + public String name() { + return "worldborder"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffWorldBorderExpand.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/effects/EffWorldBorderExpand.java similarity index 67% rename from src/main/java/ch/njol/skript/effects/EffWorldBorderExpand.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/effects/EffWorldBorderExpand.java index 045a6e514..665787619 100644 --- a/src/main/java/ch/njol/skript/effects/EffWorldBorderExpand.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/effects/EffWorldBorderExpand.java @@ -1,7 +1,6 @@ -package ch.njol.skript.effects; +package org.skriptlang.skript.bukkit.worldborder.elements.effects; import ch.njol.skript.Skript; -import ch.njol.skript.config.Node; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; @@ -17,6 +16,8 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Expand/Shrink World Border") @Description({ @@ -29,12 +30,16 @@ @Since("2.11") public class EffWorldBorderExpand extends Effect { - static { - Skript.registerEffect(EffWorldBorderExpand.class, - "(expand|grow) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]", - "(expand|grow) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]", - "(contract|shrink) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]", - "(contract|shrink) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]" + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register( + SyntaxRegistry.EFFECT, + SyntaxInfo.builder(EffWorldBorderExpand.class) + .supplier(EffWorldBorderExpand::new) + .addPatterns("(expand|grow) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]", + "(expand|grow) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]", + "(contract|shrink) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]", + "(contract|shrink) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]") + .build() ); } @@ -45,6 +50,7 @@ public class EffWorldBorderExpand extends Effect { private Expression numberExpr; private @Nullable Expression timespan; private static final double MAX_WORLDBORDER_SIZE = 59999968; + private static final boolean USE_DEPRECATED = !Skript.methodExists(WorldBorder.class, "changeSize"); @Override @SuppressWarnings("unchecked") @@ -74,20 +80,33 @@ protected void execute(Event event) { if (timespan != null) { Timespan timespan = this.timespan.getSingle(event); if (timespan != null) - speed = timespan.getAs(TimePeriod.SECOND); + if (USE_DEPRECATED) { + speed = timespan.getAs(TimePeriod.SECOND); + } else { + speed = timespan.getAs(TimePeriod.TICK); + } } WorldBorder[] worldBorders = this.worldBorders.getArray(event); if (to) { input = Math2.fit(1, input, MAX_WORLDBORDER_SIZE); - for (WorldBorder worldBorder : worldBorders) - worldBorder.setSize(input, speed); + if (USE_DEPRECATED) { + for (WorldBorder worldBorder : worldBorders) + worldBorder.setSize(input, speed); + } else { + for (WorldBorder worldBorder : worldBorders) + worldBorder.changeSize(input, speed); + } } else { if (shrink) input = -input; for (WorldBorder worldBorder : worldBorders) { double size = worldBorder.getSize(); size = Math2.fit(1, size + input, MAX_WORLDBORDER_SIZE); - worldBorder.setSize(size, speed); + if (USE_DEPRECATED) { + worldBorder.setSize(size, speed); + } else { + worldBorder.changeSize(size, speed); + } } } } diff --git a/src/main/java/ch/njol/skript/sections/ExprSecCreateWorldBorder.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprSecCreateWorldBorder.java similarity index 72% rename from src/main/java/ch/njol/skript/sections/ExprSecCreateWorldBorder.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprSecCreateWorldBorder.java index 05d5df999..9b5a73541 100644 --- a/src/main/java/ch/njol/skript/sections/ExprSecCreateWorldBorder.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprSecCreateWorldBorder.java @@ -1,18 +1,15 @@ -package ch.njol.skript.sections; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; -import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SectionExpression; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.util.SectionUtils; -import ch.njol.skript.registrations.EventValues; import ch.njol.skript.variables.Variables; import ch.njol.skript.doc.Example; import ch.njol.util.Kleenean; @@ -22,14 +19,18 @@ import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValue; +import org.skriptlang.skript.bukkit.lang.eventvalue.EventValueRegistry; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; import java.util.List; @Name("Create WorldBorder") @Description({ - "Creates a new, unused world border. World borders can be assigned to either worlds or specific players.", - "Borders assigned to worlds apply to all players in that world.", - "Borders assigned to players apply only to those players, and different players can have different borders." + "Creates a new, unused world border. World borders can be assigned to either worlds or specific players.", + "Borders assigned to worlds apply to all players in that world.", + "Borders assigned to players apply only to those players, and different players can have different borders." }) @Example(""" on join: @@ -48,9 +49,16 @@ @Since("2.11") public class ExprSecCreateWorldBorder extends SectionExpression { - static { - Skript.registerExpression(ExprSecCreateWorldBorder.class, WorldBorder.class, ExpressionType.SIMPLE, "a [virtual] world[ ]border"); - EventValues.registerEventValue(CreateWorldborderEvent.class, WorldBorder.class, CreateWorldborderEvent::getWorldBorder); + public static void register(SyntaxRegistry syntaxRegistry, EventValueRegistry eventValueRegistry) { + syntaxRegistry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprSecCreateWorldBorder.class, WorldBorder.class) + .addPattern("a [virtual] world[ ]border") + .build() + ); + eventValueRegistry.register(EventValue.builder(CreateWorldborderEvent.class, WorldBorder.class) + .getter(CreateWorldborderEvent::getWorldBorder) + .build()); } private Trigger trigger = null; @@ -58,7 +66,6 @@ public class ExprSecCreateWorldBorder extends SectionExpression { @Override public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { if (node != null) { - //noinspection unchecked trigger = SectionUtils.loadLinkedCode("create worldborder", (beforeLoading, afterLoading) -> loadCode(node, "create worldborder", beforeLoading, afterLoading, CreateWorldborderEvent.class)); return trigger != null; @@ -69,7 +76,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, @Override protected WorldBorder @Nullable [] get(Event event) { WorldBorder worldBorder = Bukkit.createWorldBorder(); - if (trigger == null) + if (trigger == null) return new WorldBorder[] {worldBorder}; CreateWorldborderEvent worldborderEvent = new CreateWorldborderEvent(worldBorder); Variables.withLocalVariables(event, worldborderEvent, () -> TriggerItem.walk(trigger, worldborderEvent)); diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorder.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorder.java similarity index 77% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorder.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorder.java index 90ee78c97..7a998db6e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorder.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorder.java @@ -1,5 +1,6 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; +import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -12,6 +13,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("World Border") @Description({ @@ -22,10 +24,15 @@ @Since("2.11") public class ExprWorldBorder extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorder.class, WorldBorder.class, "world[ ]border", "worlds/players"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorder.class, WorldBorder.class, "world[ ]border", "worlds/players", true) + .supplier(ExprWorldBorder::new) + .build()); } + private static final boolean USE_DEPRECATED = !Skript.methodExists(WorldBorder.class, "getWarningTimeTicks"); + @Override public @Nullable WorldBorder convert(Object object) { if (object instanceof World world) { @@ -64,7 +71,11 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { worldBorder.setDamageAmount(to.getDamageAmount()); worldBorder.setDamageBuffer(to.getDamageBuffer()); worldBorder.setWarningDistance(to.getWarningDistance()); - worldBorder.setWarningTime(to.getWarningTime()); + if (USE_DEPRECATED) { + worldBorder.setWarningTime(to.getWarningTime()); + } else { + worldBorder.setWarningTimeTicks(to.getWarningTimeTicks()); + } } else if (object instanceof Player player) { player.setWorldBorder(to); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderCenter.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderCenter.java similarity index 83% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderCenter.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderCenter.java index c2053e8f8..a8ce24b43 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderCenter.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderCenter.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -11,6 +11,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Center of World Border") @Description("The center of a world border.") @@ -18,8 +19,11 @@ @Since("2.11") public class ExprWorldBorderCenter extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderCenter.class, Location.class, "world[ ]border (center|middle)", "worldborders"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderCenter.class, Location.class, "world[ ]border (center|middle)", "worldborders", true) + .supplier(ExprWorldBorderCenter::new) + .build()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageAmount.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageAmount.java similarity index 83% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageAmount.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageAmount.java index cac81576d..52c3a1a53 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageAmount.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageAmount.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -10,6 +10,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Damage Amount of World Border") @Description({ @@ -20,9 +21,12 @@ @Since("2.11") public class ExprWorldBorderDamageAmount extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderDamageAmount.class, Double.class, "world[ ]border damage amount", "worldborders"); - } + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderDamageAmount.class, Double.class, "world[ ]border damage amount", "worldborders", true) + .supplier(ExprWorldBorderDamageAmount::new) + .build()); + } @Override public @Nullable Double convert(WorldBorder worldBorder) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageBuffer.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageBuffer.java similarity index 82% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageBuffer.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageBuffer.java index da1c1cee0..f0c2f3230 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderDamageBuffer.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderDamageBuffer.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -10,6 +10,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Damage Buffer of World Border") @Description({ @@ -20,9 +21,12 @@ @Since("2.11") public class ExprWorldBorderDamageBuffer extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderDamageBuffer.class, Double.class, "world[ ]border damage buffer", "worldborders"); - } + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderDamageBuffer.class, Double.class, "world[ ]border damage buffer", "worldborders", true) + .supplier(ExprWorldBorderDamageBuffer::new) + .build()); + } @Override public @Nullable Double convert(WorldBorder worldBorder) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderSize.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderSize.java similarity index 85% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderSize.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderSize.java index 13424d028..e8780d168 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderSize.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderSize.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; @@ -15,6 +15,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Size of World Border") @Description({ @@ -25,8 +26,11 @@ @Since("2.11") public class ExprWorldBorderSize extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderSize.class, Double.class, "world[ ]border (size|diameter|:radius)", "worldborders"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderSize.class, Double.class, "world[ ]border (size|diameter|:radius)", "worldborders", true) + .supplier(ExprWorldBorderSize::new) + .build()); } private boolean radius; diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningDistance.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningDistance.java similarity index 86% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningDistance.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningDistance.java index 53f52ecb0..b2fd1702d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningDistance.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningDistance.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; @@ -11,6 +11,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Warning Distance of World Border") @Description({ @@ -21,8 +22,11 @@ @Since("2.11") public class ExprWorldBorderWarningDistance extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderWarningDistance.class, Integer.class, "world[ ]border warning distance", "worldborders"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderWarningDistance.class, Integer.class, "world[ ]border warning distance", "worldborders", true) + .supplier(ExprWorldBorderWarningDistance::new) + .build()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningTime.java b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningTime.java similarity index 52% rename from src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningTime.java rename to src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningTime.java index bac607fbf..96fd77d9f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldBorderWarningTime.java +++ b/src/main/java/org/skriptlang/skript/bukkit/worldborder/elements/expressions/ExprWorldBorderWarningTime.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.worldborder.elements.expressions; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; @@ -14,6 +14,7 @@ import org.bukkit.WorldBorder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; @Name("Warning Time of World Border") @Description("The warning time of a world border. If the border is shrinking, the player's screen will be tinted red once the border will catch the player within this time period.") @@ -21,13 +22,21 @@ @Since("2.11") public class ExprWorldBorderWarningTime extends SimplePropertyExpression { - static { - registerDefault(ExprWorldBorderWarningTime.class, Timespan.class, "world[ ]border warning time", "worldborders"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, + infoBuilder(ExprWorldBorderWarningTime.class, Timespan.class, "world[ ]border warning time", "worldborders", true) + .supplier(ExprWorldBorderWarningTime::new) + .build()); } + private static final boolean USE_DEPRECATED = !Skript.methodExists(org.bukkit.WorldBorder.class,"getWarningTimeTicks"); + @Override public @Nullable Timespan convert(WorldBorder worldBorder) { - return new Timespan(TimePeriod.SECOND, worldBorder.getWarningTime()); + if (USE_DEPRECATED) { + return new Timespan(TimePeriod.SECOND, worldBorder.getWarningTime()); + } + return new Timespan(TimePeriod.TICK, worldBorder.getWarningTimeTicks()); } @Override @@ -40,12 +49,27 @@ public class ExprWorldBorderWarningTime extends SimplePropertyExpression input; - case ADD -> Math2.addClamped(worldBorder.getWarningTime(), input); - case REMOVE -> Math2.addClamped(worldBorder.getWarningTime(), -input); + case ADD -> { + if (USE_DEPRECATED) { + yield Math2.addClamped(worldBorder.getWarningTime(), input); + } + yield Math2.addClamped(worldBorder.getWarningTimeTicks(), input); + } + case REMOVE -> { + if (USE_DEPRECATED) { + yield Math2.addClamped(worldBorder.getWarningTime(), -input); + } + yield Math2.addClamped(worldBorder.getWarningTimeTicks(), -input); + } default -> throw new IllegalStateException(); }; setWarningTime(worldBorder, warningTime); @@ -53,11 +77,14 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { } private static void setWarningTime(WorldBorder worldBorder, long inputTime) { - // make sure this won't cause an overflow, as internal value is in ticks - long time = Math2.multiplyClamped(inputTime, 20); - // fit and convert back to seconds - int warningTime = ((int) Math2.fit(0, time, Integer.MAX_VALUE)) / 20; - worldBorder.setWarningTime(warningTime); + if (USE_DEPRECATED) { + long time = Math2.multiplyClamped(inputTime, 20); + int warningTime = ((int) Math2.fit(0, time, Integer.MAX_VALUE)) / 20; + worldBorder.setWarningTime(warningTime); + } else { + int warningTime = (int) Math2.fit(0, inputTime, Integer.MAX_VALUE); + worldBorder.setWarningTimeTicks(warningTime); + } } @Override diff --git a/src/main/java/org/skriptlang/skript/common/CommonModule.java b/src/main/java/org/skriptlang/skript/common/CommonModule.java index c3569dc0b..762ec04c1 100644 --- a/src/main/java/org/skriptlang/skript/common/CommonModule.java +++ b/src/main/java/org/skriptlang/skript/common/CommonModule.java @@ -32,7 +32,8 @@ protected void loadSelf(SkriptAddon addon) { register(addon, ExprColorFromHexCode::register, ExprHexCode::register, - ExprRecursiveSize::register + ExprRecursiveSize::register, + ExprReplace::register ); } diff --git a/src/main/java/org/skriptlang/skript/common/elements/expressions/ExprReplace.java b/src/main/java/org/skriptlang/skript/common/elements/expressions/ExprReplace.java new file mode 100644 index 000000000..828b704bf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/elements/expressions/ExprReplace.java @@ -0,0 +1,153 @@ +package org.skriptlang.skript.common.elements.expressions; + +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.DefaultSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Name("Text Replacement") +@Description("Performs a text replacement on a given value, returning the result. Supports regex and case sensitive replacement.") +@Example("send \"Welcome [player]\" where \"[player]\" is replaced with \"%player%\" to player") +@Example(""" + # Function for sanitizing user inputs + # Strips the input of any non-alphanumeric characters using regex + function sanitizeInput(input: string) :: string: + return {_input} where regex pattern "\\W" is replaced with "" + """) +@Example(""" + # Function to convert &# hex color codes to <# > (mini message format) + function colorFormat(input: string) :: string: + return {_input} where all instances of regex pattern "&#([a-fA-F0-9]{6})" are replaced with "<#$1>" + """) +@Example(""" + # Very simple chat censor + on chat: + set message to message where all instances of "idiot", "noob" are replaced with "****" + set message to message where regex "\\b(idiot|noob)\\b" is replaced with "****" # Regex version using word boundaries for better results + """) +@Since("2.16") +public class ExprReplace extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + DefaultSyntaxInfos.Expression.builder(ExprReplace.class, String.class) + .addPatterns( + "%strings% where [(first:[the] first instance[s]|all instances) of] %strings% (is|are) replaced with %string% [regex:using regex|case:with case sensitivity]", + "%strings% where [(first:[the] first instance[s]|all instances) of] regex [pattern[s]] %strings% (is|are) replaced with %string%" + ) + .supplier(ExprReplace::new) + .build() + ); + } + + private Expression needleExpr; + private Expression haystackExpr; + private Expression replacementExpr; + + private boolean isFirst; + private boolean isRegex; + private boolean isCaseSensitive; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expr, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + haystackExpr = (Expression) expr[0]; + needleExpr = (Expression) expr[1]; + replacementExpr = (Expression) expr[2]; + + isRegex = matchedPattern == 1 || parseResult.hasTag("regex"); + isFirst = parseResult.hasTag("first"); + isCaseSensitive = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); + return true; + } + + @Override + protected String @Nullable [] get(Event event) { + String replacement = replacementExpr.getSingle(event); + String[] needles = needleExpr.getArray(event); + String[] haystacks = haystackExpr.getArray(event); + + if (replacement == null) { + return haystacks; + } + + List result = new ArrayList<>(haystacks.length); + + if (isRegex) { + List patterns = new ArrayList<>(needles.length); + for (String needle : needles) { + try { // Pre compile regex for use with multiple haystacks + patterns.add(Pattern.compile(needle)); + } catch (Exception ignored) { + } + } + + for (String haystack : haystacks) { + for (Pattern pattern : patterns) { + Matcher matcher = pattern.matcher(haystack); + try { // Throws IndexOutOfBounds on improper use of regex groups in replacement + if (isFirst) { + haystack = matcher.replaceFirst(replacement); + } else { + haystack = matcher.replaceAll(replacement); + } + } catch (IndexOutOfBoundsException ignored) {} + } + result.add(haystack); + } + } else { + for (String haystack : haystacks) { + for (String needle : needles) { + if (isFirst) { + haystack = StringUtils.replaceFirst(haystack, needle, replacement, isCaseSensitive); + } else { + haystack = StringUtils.replace(haystack, needle, replacement, isCaseSensitive); + } + } + result.add(haystack); + } + } + + return result.toArray(new String[0]); + } + + @Override + public boolean isSingle() { + return haystackExpr.isSingle(); + } + + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("replace") + .appendIf(isFirst, "first") + .append(needleExpr, "in", haystackExpr, "with", replacementExpr) + .appendIf(isRegex, "using regex") + .appendIf(isCaseSensitive, "with case sensitivity") + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/properties/PropertiesModule.java b/src/main/java/org/skriptlang/skript/common/properties/PropertiesModule.java index 9417a77f5..066af42dc 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/PropertiesModule.java +++ b/src/main/java/org/skriptlang/skript/common/properties/PropertiesModule.java @@ -24,8 +24,12 @@ protected void loadSelf(SkriptAddon addon) { PropExprCustomName::register, PropExprName::register, PropExprNumber::register, + PropExprProgress::register, PropExprSize::register, + PropExprStyle::register, + PropExprTitle::register, PropExprValueOf::register, + PropExprViewers::register, PropExprWXYZ::register ); } diff --git a/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprProgress.java b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprProgress.java new file mode 100644 index 000000000..f037b48b3 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprProgress.java @@ -0,0 +1,33 @@ +package org.skriptlang.skript.common.properties.elements.expressions; + +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Progress") +@Description(""" + Represents the progress of something. + """) +@Example(""" + set progress of event-bossbar to 75% + """) +@Since("2.16") +@RelatedProperty("progress") +public class PropExprProgress extends PropertyBaseExpression> { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + PropertyExpression.infoBuilder(PropExprProgress.class, Object.class, "progress", "objects", false) + .supplier(PropExprProgress::new) + .build()); + } + + @Override + public Property> getProperty() { + return Property.PROGRESS; + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprStyle.java b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprStyle.java new file mode 100644 index 000000000..6ecb4f095 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprStyle.java @@ -0,0 +1,33 @@ +package org.skriptlang.skript.common.properties.elements.expressions; + +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Style") +@Description(""" + Represents the style of something. + """) +@Example(""" + set style of event-bossbar to 6 notches + """) +@Since("2.16") +@RelatedProperty("style") +public class PropExprStyle extends PropertyBaseExpression> { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + PropertyExpression.infoBuilder(PropExprStyle.class, Object.class, "style[s]", "objects", false) + .supplier(PropExprStyle::new) + .build()); + } + + @Override + public Property> getProperty() { + return Property.STYLE; + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprTitle.java b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprTitle.java new file mode 100644 index 000000000..506ea2690 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprTitle.java @@ -0,0 +1,39 @@ +package org.skriptlang.skript.common.properties.elements.expressions; + +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Title") +@Description(""" + Represents the title of something. + """) +@Example(""" + set title of event-bossbar to "hello!" + """) +@Example(""" + on inventory click: + if type of event-item is written book: + set title of event-item to formatted "%random uuid%" + send "Your book's title is now forever forgotten.." to player + """) +@Since("2.16") +@RelatedProperty("title") +public class PropExprTitle extends PropertyBaseExpression> { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + PropertyExpression.infoBuilder(PropExprTitle.class, Object.class, "title[s]", "objects", false) + .supplier(PropExprTitle::new) + .build()); + } + + @Override + public Property> getProperty() { + return Property.TITLE; + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprViewers.java b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprViewers.java new file mode 100644 index 000000000..0660793ee --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/properties/elements/expressions/PropExprViewers.java @@ -0,0 +1,38 @@ +package org.skriptlang.skript.common.properties.elements.expressions; + +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Viewers") +@Description(""" + Represents something that is viewing something. + """) +@Example(""" + add {_player} to viewers of event-bossbar + """) +@Example(""" + on inventory open: + set {_mylist::*} to viewers of event-inventory + send "%size of {_list::*}% other players are viewing this inventory!" + """) +@Since("2.16") +@RelatedProperty("viewers") +public class PropExprViewers extends PropertyBaseExpression> { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, + PropertyExpression.infoBuilder(PropExprViewers.class, Object.class, "viewer[s]", "objects", false) + .supplier(PropExprViewers::new) + .build()); + } + + @Override + public Property> getProperty() { + return Property.VIEWERS; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/Property.java b/src/main/java/org/skriptlang/skript/lang/properties/Property.java index a8049ba0a..80ed49c26 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/Property.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/Property.java @@ -276,20 +276,65 @@ public record PropertyInfo>(Property Skript.instance(), ExpressionPropertyHandler.class); + + /** + * A property for getting the title of something + */ + public static final Property> TITLE = Property.of( + "title", + "The title of something.", + "2.16", + Skript.instance(), + ExpressionPropertyHandler.class); + + /** + * A property for getting the progress of something + */ + public static final Property> PROGRESS = Property.of( + "progress", + "The progress of something.", + "2.16", + Skript.instance(), + ExpressionPropertyHandler.class); + + /** + * A property for getting the style of something + */ + public static final Property> STYLE = Property.of( + "style", + "The style of something.", + "2.16", + Skript.instance(), + ExpressionPropertyHandler.class); + + /** + * A property for getting the viewers of something + */ + public static final Property> VIEWERS = Property.of( + "viewers", + "The viewers of something.", + "2.16", + Skript.instance(), + ExpressionPropertyHandler.class); + /** * Register all Skript's default properties. Should be done prior to loading classinfos. */ public static void registerDefaultProperties() { - NAME.register(); - DISPLAY_NAME.register(); - CONTAINS.register(); AMOUNT.register(); - SIZE.register(); - NUMBER.register(); + CONTAINS.register(); + DISPLAY_NAME.register(); IS_EMPTY.register(); - TYPED_VALUE.register(); + NAME.register(); + NUMBER.register(); + PROGRESS.register(); SCALE.register(); + SIZE.register(); SPEED.register(); + STYLE.register(); + TITLE.register(); + TYPED_VALUE.register(); + VIEWERS.register(); } } diff --git a/src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfos.java b/src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfos.java index 87927c4e5..e161e67de 100644 --- a/src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfos.java +++ b/src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfos.java @@ -6,6 +6,8 @@ import org.skriptlang.skript.registration.DefaultSyntaxInfosImpl.ExpressionImpl; import org.skriptlang.skript.registration.DefaultSyntaxInfosImpl.StructureImpl; +import java.util.function.Supplier; + /** * Contains interfaces describing syntax infos for specific types of syntax. */ @@ -19,6 +21,24 @@ public sealed interface DefaultSyntaxInfos permits SyntaxInfo { */ interface Expression, R> extends SyntaxInfo { + /** + * Constructs a simple {@link ch.njol.skript.lang.Expression} syntax info for a class + * from return type information and patterns. + * @param expressionClass The Expression class the info will represent. + * @param instanceSupplier A supplier for creating new instances of {@code type}. + * @param returnType The class representing the supertype of all values the Expression may return. + * @param patterns Patterns describing the syntax. + * @return A syntax info representing {@code type}. + */ + @Contract("_, _, _ , _-> new") + static , R> Expression simple(Class expressionClass, + Supplier instanceSupplier, Class returnType, String... patterns) { + return builder(expressionClass, returnType) + .supplier(instanceSupplier) + .addPatterns(patterns) + .build(); + } + /** * Constructs a builder for an expression syntax info. * @param expressionClass The Expression class the info will represent. @@ -109,6 +129,22 @@ public boolean canBeSection() { } + /** + * Constructs a simple {@link org.skriptlang.skript.lang.structure.Structure} syntax info for a class from patterns. + * @param structureClass The Structure class the info will represent. + * @param instanceSupplier A supplier for creating new instances of {@code type}. + * @param patterns Patterns describing the syntax. + * @return A syntax info representing {@code type}. + */ + @Contract("_, _, _ -> new") + static Structure simple(Class structureClass, + Supplier instanceSupplier, String... patterns) { + return builder(structureClass) + .supplier(instanceSupplier) + .addPatterns(patterns) + .build(); + } + /** * Constructs a builder for a structure syntax info. * @param structureClass The Structure class the info will represent. diff --git a/src/main/java/org/skriptlang/skript/registration/SyntaxInfo.java b/src/main/java/org/skriptlang/skript/registration/SyntaxInfo.java index ad6aa368b..ac865d6a0 100644 --- a/src/main/java/org/skriptlang/skript/registration/SyntaxInfo.java +++ b/src/main/java/org/skriptlang/skript/registration/SyntaxInfo.java @@ -37,6 +37,21 @@ public non-sealed interface SyntaxInfo extends DefaultS */ Priority PATTERN_MATCHES_EVERYTHING = Priority.after(COMBINED); + /** + * Constructs a simple syntax info for a class from patterns. + * @param type The syntax class the info will represent. + * @param instanceSupplier A supplier for creating new instances of {@code type}. + * @param patterns Patterns describing the syntax. + * @return A syntax info representing {@code type}. + */ + @Contract("_, _, _ -> new") + static SyntaxInfo simple(Class type, Supplier instanceSupplier, String... patterns) { + return builder(type) + .supplier(instanceSupplier) + .addPatterns(patterns) + .build(); + } + /** * Constructs a builder for a syntax info. * @param type The syntax class the info will represent. diff --git a/src/main/java/org/skriptlang/skript/util/LabColor.java b/src/main/java/org/skriptlang/skript/util/LabColor.java new file mode 100644 index 000000000..c6e875877 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/util/LabColor.java @@ -0,0 +1,132 @@ +package org.skriptlang.skript.util; + +import org.joml.Matrix3d; +import org.joml.Matrix3dc; +import org.joml.Vector3d; +import org.joml.Vector3dc; + +/** + * Credit to LabColor + * for this. + */ +public record LabColor( + double L, + double a, + double b +) { + + /** + * Eqn_RGB_XYZ_Matrix + * sRGB Working Space, D65 2° Reference White Point + */ + private static final Matrix3dc XYZ_TRANSFORMATION = new Matrix3d( + 0.4124564, 0.3575761, 0.1804375, + 0.2126729, 0.7151522, 0.0721750, + 0.0193339, 0.1191920, 0.9503041 + ).transpose(); + + private static final Matrix3dc INVERSE_XYZ_TRANSFORMATION = new Matrix3d( + 3.2404542, -1.5371385, -0.4985314, + -0.9692660, 1.8760108, 0.0415560, + 0.0556434, -0.2040259, 1.0572252 + ).transpose(); + + /** + * D65_values + */ + private static final Vector3dc D65 = new Vector3d(0.95047, 1, 1.08883); + + /** + * LContinuity + */ + private static final double EPSILON = 0.008856; + private static final double K_0 = 903.3; + private static final double K_1 = 7.787; + + private static final double POW25_7 = 6103515625.0; + + public int toRGB() { + double fy = (L + 16) / 116; + double fx = a / 500 + fy; + double fz = fy - b / 200; + + double xr = fInverse(fx); + double yr = L > K_0 * EPSILON ? fy * fy * fy : L / K_0; + double zr = fInverse(fz); + + Vector3d rgb = new Vector3d(xr * D65.x(), yr * D65.y(), zr * D65.z()); + rgb.mul(INVERSE_XYZ_TRANSFORMATION); + + double r = inverseGammaCorrection(rgb.x()); + double g = inverseGammaCorrection(rgb.y()); + double b = inverseGammaCorrection(rgb.z()); + + int ri = (int) Math.round(Math.clamp(r, 0, 1) * 255); + int gi = (int) Math.round(Math.clamp(g, 0, 1) * 255); + int bi = (int) Math.round(Math.clamp(b, 0, 1) * 255); + + return ri << 16 | gi << 8 | bi; + } + + public double euclideanDistanceSquared(LabColor other) { + double L = other.L() - L(); + double a = other.a() - a(); + double b = other.b() - b(); + return L * L + a * a + b * b; + } + + public double euclideanDistance(LabColor other) { + return Math.sqrt(euclideanDistanceSquared(other)); + } + + private double c() { + return Math.sqrt(a * a + b * b); + } + + public static LabColor fromRGB(int rgb) { + return fromRGB( + rgb >> 16 & 0xFF, + rgb >> 8 & 0xFF, + rgb & 0xFF + ); + } + + public static LabColor fromRGB(int r, int g, int b) { + return fromRGB(r / 255.0, g / 255.0, b / 255.0); + } + + public static LabColor fromRGB(double r, double g, double b) { + double rLinear = gammaCorrection(r); + double gLinear = gammaCorrection(g); + double bLinear = gammaCorrection(b); + Vector3d xyz = new Vector3d(rLinear, gLinear, bLinear); + xyz.mul(XYZ_TRANSFORMATION); + double Y = f(xyz.y() / D65.y()); + double LStar = 116 * Y - 16; + double aStar = 500 * (f(xyz.x() / D65.x()) - Y); + double bStar = 200 * (Y - f(xyz.z() / D65.z())); + return new LabColor(LStar, aStar, bStar); + } + + private static double gammaCorrection(double value) { + if (value > 0.04045) + return Math.pow((value + 0.055) / 1.055, 2.4); + return value / 12.92; + } + + private static double inverseGammaCorrection(double value) { + if (value > 0.0031308) + return 1.055 * Math.pow(value, 1.0 / 2.4) - 0.055; + return 12.92 * value; + } + + private static double f(double t) { + return t > EPSILON ? Math.cbrt(t) : (K_1 * t) + 16.0 / 116.0; + } + + private static double fInverse(double t) { + double n3 = t * t * t; + return n3 > EPSILON ? n3 : (t - (16 / 116.0)) / K_1; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 7a9a6cb71..d72d5ecb0 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -286,72 +286,44 @@ biomes: cherry_grove: cherry grove # 1.21.3 pale_garden: pale garden + # 26.2 + sulfur_caves: sulfur caves # -- Tree Types -- tree types: - tree: - name: tree¦s - pattern: (any )?trees? - regular: - name: oak¦s - pattern: (any )?(regular|normal|oak)( tree)?s? - small_regular: - name: small oak¦s - pattern: small (regular|normal|oak)( tree)?s? - big_regular: - name: big tree¦s - pattern: (big|large) (regular|normal|oak)( tree)?s? - redwood: - name: redwood¦s - pattern: (any )?(fir|pine|spruce|redwood)( tree)?s? - small_redwood: - name: small redwood¦s - pattern: small (fir|pine|spruce|redwood)( tree)?s? - big_redwood: - name: tall redwood¦s - pattern: (big|tall) (fir|pine|spruce|redwood)( tree)?s? - mega_redwood: - name: mega redwood¦s - pattern: (mega|giant) (fir|pine|spruce|redwood)( tree)?s? - birch: - name: birch¦es - pattern: birch( trees?|es) - tall_birch: - name: tall birch¦es - pattern: (tall|big) birch( trees?|es) - jungle: - name: jungle tree¦s - pattern: (any )?jungle( tree)?s? - small_jungle: - name: small jungle tree¦s - pattern: small jungle( tree)?s? - big_jungle: - name: big jungle tree¦s - pattern: (big|tall|huge|giant|large) jungle( tree)?s? - cocoa_tree: - name: cocoa tree¦s - pattern: cocoa trees? - acacia: - name: acacia¦s - pattern: acacia( tree)?s? - dark_oak: - name: dark oak¦s - pattern: dark oak( tree)?s? - jungle_bush: - name: bush¦es - pattern: (jungle )?bush(es)? - swamp: - name: swamp tree¦s - pattern: swamp trees? - mushroom: - name: huge mushroom¦s - pattern: (any )?(huge|giant|large|big) mushrooms? - red_mushroom: - name: huge red mushroom¦s - pattern: (huge|giant|large|big) red mushrooms? - brown_mushroom: - name: huge brown mushroom¦s - pattern: (huge|giant|large|big) brown mushrooms? + tree: tree, any tree + oak: oak tree, any oak tree, any normal tree, any regular tree + small_oak: small oak tree, small normal tree, small regular tree + big_oak: big oak tree, big normal tree, big regular tree + spruce: spruce tree, any spruce tree + small_spruce: small spruce tree + big_spruce: big spruce tree, tall spruce tree + mega_spruce: mega spruce tree, giant spruce tree + birch: birch tree, any birch tree + small_birch: small birch tree + tall_birch: tall birch tree + jungle: jungle tree, any jungle tree + small_jungle: small jungle tree + big_jungle: big jungle tree, tall jungle tree, giant jungle tree + cocoa_tree: cocoa tree, jungle cocoa tree, cocoa jungle tree + jungle_bush: jungle bush, jungle bush tree + acacia: acacia tree + dark_oak: dark oak tree + swamp: swamp tree, swamp oak, swamp oak tree + mushroom: huge mushroom, any huge mushroom + red_mushroom: huge red mushroom + brown_mushroom: huge brown mushroom + mangrove: mangrove tree, any mangrove treee + small_mangrove: small mangrove tree + big_mangrove: big mangrove tree + azalea: azalea tree + pale_oak: pale oak tree, any pale oak tree + pale_oak_normal: normal pale oak tree + pale_oak_creaking: creaking pale oak tree + cherry: cherry tree + crimson_fungus: crimson fungus, crimson tree, crimson fungus tree + warped_fungus: warped fungus, warped tree, warped fungus tree + chorus_plant: chorus plant # -- Time -- time: @@ -1626,6 +1598,11 @@ entities: name: parched¦s pattern: parched[plural:s] + # 26.2 + sulfur cube: + name: sulfur cube¦s + pattern: sulfur cube[plural:s] + # SuperTypes human: name: human¦s @@ -2096,6 +2073,12 @@ attribute types: camera_distance: camera distance waypoint_transmit_range: waypoint transmit range waypoint_receive_range: waypoint receive range + # 26.2 + bounciness: bounciness + friction_modifier: friction modifier + below_name_distance: below name distance, nameplate score visibility distance, name tag score visibility distance + air_drag_modifier: air drag modifier + name_tag_distance: name tag distance, nameplate visibility distance, name tag visibility distance # -- Environments -- environments: @@ -2286,6 +2269,25 @@ game effect: trial_spawner_become_ominous: trial spawner become ominous effect trial_spawner_spawn_item: trial spawner spawn item effect + # 26.2 + dispenser_dispense: dispenser dispense effect + dispenser_fail: dispenser fail effect, dispenser fail to dispense effect, dispenser failure to dispense effect + dispenser_projectile_launch: dispenser projectile launch effect + record_stop: record stop effect, music disc stop effect + ender_dragon_shoot: ender dragon shoot effect, ender dragon shoots fireball effect + zombie_converted_to_villager: zombie converted to villager effect, zombie villager cured effect + wind_charge_shoot: wind charge shoot effect + sulfur_spike_land: sulfur spike land effect + smoke_shoot: smoke shoot effect, smoke eruption effect + destroy_block: destroy block effect, block destruction effect + ender_dragon_breath: ender dragon breath effect, ender dragon breathing effect + white_smoke_shoot: white smoke shoot effect, white smoke eruption effect + ender_dragon_growl: ender dragon growl effect, ender dragon growling effect + sculk_charge: sculk charge effect, + sculk_shriek: sculk shriek effect, + brush_block_complete: brush block complete effect, finish brushing block effect, + egg_crack: egg crack effect + # -- Entity Effects -- # all patterns should include the word 'effect' or 'animation' # 'effect' should be used for particles, 'sound effect' for sounds, 'animation' for model animations @@ -2505,6 +2507,15 @@ particle: white_smoke: white smoke particle¦s @a witch: witch particle¦s @a + # 26.2 + noxious_gas: noxious gas particle¦s @a, nausea gas particle¦s @a + noxious_gas_cloud: noxious gas cloud¦s @a, nausea gas cloud particle¦s @a + sulfur_cube_goo: sulfur cube goo particle¦s @a, sulfur cube jump particle¦s @a, sulfur cube jumping particle¦s @a + sulfur_bubbles: sulfur bubbles particle¦s @a, sulfur bubble column particle¦s @a + geyser: geyser particle¦s @a + geyser_base: geyser base particle¦s @a + geyser_plume: geyser plume particle¦s @a + geyser_poof: geyser poof particle¦s @a # -- Teleport Flags -- teleport flags: @@ -2717,6 +2728,15 @@ villager career change reasons: employed: employment losing_job: losing job +# -- Boss Bars -- + +boss bar styles: + segmented_6: 6 segments, six segments, notched with 6 segments, notched with six segments, 6 notches, six notches + segmented_10: 10 segments, ten segments, notched with 10 segments, notched with ten segments, 10 notches, ten notches + segmented_12: 12 segments, twelve segments, notched with 12 segments, notched with twelve segments, 12 notches, twelve notches + segmented_20: 20 segments, twenty segments, notched with 20 segments, notched with twenty segments, 20 notches, twenty notches + solid: solid, no notches, progress + # -- Damage Types -- damage types: arrow: arrow @@ -2769,6 +2789,8 @@ damage types: wind_charge: wind charge wither: wither wither_skull: wither skull + # 26.2 + sulfur_cube_hot: sulfur cube hot, hot sulfur cube, magma sulfur cube # -- Boolean -- boolean: @@ -2830,6 +2852,9 @@ types: itementity: dropped item¦s @a # same as entities.dropped item.name itemflag: item flag¦s @an biome: biome¦s @a + bossbar: boss bar¦s @a + keyedbossbar: keyed boss bar¦s @a + bossbarstyle: boss bar style¦s @a potioneffecttype: potion effect type¦s @a potioneffecttypecategory: potion effect type categor¦y¦ies @a potioneffect: potion effect¦s @a @@ -2918,7 +2943,7 @@ types: direction: direction¦s @a slot: slot¦s @a color: color¦s @a - structuretype: tree type¦s @a + treetype: tree type¦s @a enchantmenttype: enchantment type¦s @an experience: experience point¦s @an experience.pattern: (e?xp|experience( points?)?) diff --git a/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java b/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java index edda38d81..0d232e507 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java @@ -18,7 +18,7 @@ import ch.njol.skript.util.Direction; import ch.njol.skript.util.Experience; import ch.njol.skript.util.SkriptColor; -import ch.njol.skript.util.StructureType; +import ch.njol.skript.util.TreeSpecies; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timeperiod; import ch.njol.skript.util.Timespan; @@ -34,7 +34,7 @@ public void serializationTest() { "String", // Skript - SkriptColor.BLACK, StructureType.RED_MUSHROOM, WeatherType.THUNDER, + SkriptColor.BLACK, TreeSpecies.RED_MUSHROOM, WeatherType.THUNDER, new Date(System.currentTimeMillis()), new Timespan(1337), new Time(12000), new Timeperiod(1000, 23000), new Experience(15), new Direction(0, Math.PI, 10), new Direction(new double[] {0, 1, 0}), new EntityType(new SimpleEntityData(HumanEntity.class), 300), diff --git a/src/test/skript/environments/java21/paper-1.21.3.json b/src/test/skript/environments/java25/paper-26.2.json similarity index 85% rename from src/test/skript/environments/java21/paper-1.21.3.json rename to src/test/skript/environments/java25/paper-26.2.json index ace6be5d2..a0590c95d 100644 --- a/src/test/skript/environments/java21/paper-1.21.3.json +++ b/src/test/skript/environments/java25/paper-26.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.21.3", + "name": "paper-26.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.21.3", + "version": "26.2", "target": "paperclip.jar" } ], diff --git a/src/test/skript/tests/bukkit/bossbar/EffBossBarFlags.sk b/src/test/skript/tests/bukkit/bossbar/EffBossBarFlags.sk new file mode 100644 index 000000000..7d9ee653d --- /dev/null +++ b/src/test/skript/tests/bukkit/bossbar/EffBossBarFlags.sk @@ -0,0 +1,28 @@ + +# Includes tests for CondHasBossBarFlag & EffBossBarFlags +test "bossbar flags": + set {_bar} to a bossbar: + make event-bossbar darken the sky + make event-bossbar create fog + make event-bossbar play boss music + make {_bar} darken the sky + make {_bar} create fog + make {_bar} play boss music + assert {_bar} darkens the sky with "Failed to set bossbar flag 'darken the sky'" + assert {_bar} does create fog with "Failed to set bossbar flag 'create fog'" + assert {_bar} does play boss music with "Failed to set bossbar flag 'play boss music'" + + make {_bar} not darken the sky + assert {_bar} does not darken the sky with "Failed to remove bossbar flag 'darken the sky'" + assert {_bar} does create fog with "Removing 'darken the sky' flag caused 'create fog' flag to be removed" + assert {_bar} does play boss music with "Removing 'darken the sky' flag caused 'play boss music' flag to be removed" + + make {_bar} not create fog + assert {_bar} does not create fog with "Failed to remove bossbar flag 'create fog'" + assert {_bar} does not darken the sky with "Removing 'create fog' flag caused 'darken the sky' flag to be removed" + assert {_bar} does play boss music with "Removing 'create fog' flag caused 'play boss music' flag to be removed" + + make {_bar} not play boss music + assert {_bar} does not play boss music with "Failed to remove bossbar flag 'play boss music'" + assert {_bar} does not darken the sky with "Removing 'play boss music' flag caused 'darken the sky' flag to be removed" + assert {_bar} does not create fog with "Removing 'play boss music' flag caused 'create fog' flag to be removed" diff --git a/src/test/skript/tests/bukkit/bossbar/ExprBossBarFromEntity.sk b/src/test/skript/tests/bukkit/bossbar/ExprBossBarFromEntity.sk new file mode 100644 index 000000000..e09a39b93 --- /dev/null +++ b/src/test/skript/tests/bukkit/bossbar/ExprBossBarFromEntity.sk @@ -0,0 +1,10 @@ + +test "bossbar from entity": + spawn wither at test-location: + set {_e} to entity + + assert boss bar of {_e} is set with "Failed to get boss bar of wither" + set {_bar} to boss bar of {_e} + set title of {_bar} to "Testing" + assert title of {_bar} is "Testing" with "Failed to set title of boss bar of wither" + clear entity in {_e} diff --git a/src/test/skript/tests/bukkit/bossbar/ExprSecCreateBossBar.sk b/src/test/skript/tests/bukkit/bossbar/ExprSecCreateBossBar.sk new file mode 100644 index 000000000..b1353e836 --- /dev/null +++ b/src/test/skript/tests/bukkit/bossbar/ExprSecCreateBossBar.sk @@ -0,0 +1,42 @@ +using error catching + +test "create bossbar": + set {_bar} to red bossbar + assert {_bar} is set with "Failed to create bossbar" + set {_error} to "Could not round to a color that is similar to rgb 18, 18, 17" + set {_bar1} to boss bar + catch runtime errors: + set color of {_bar1} to rgb(18, 18, 17) + assert last caught errors is {_error} with "set color of bar to rgb(18, 18, 17) should error but did not" + set {_bar1} to bossbar + set {_bar2} to a boss bar: + set color of event-bossbar to red + set progress of event-bossbar to 50% + set style of event-bossbar to 6 notches + set title of event-bossbar to "testing" + assert color of {_bar2} is red with "Failed to set bossbar color" + assert progress of {_bar2} is 50% with "Failed to set bossbar progress" + assert style of {_bar2} is 6 notches with "Failed to set bossbar style" + assert title of {_bar2} is "testing" with "Failed to set bossbar title" + + set {_bar3} to a keyed boss bar with key "testbar": + set title of event-bossbar to "testing3" + set progress of event-bossbar to 75% + set style of event-bossbar to 20 notches + set color of event-bossbar to green + assert {_bar3} is set with "Failed to create keyed bossbar" + assert all keyed bossbars contains {_bar3} with "Bossbar was not listed in all bossbars" + assert bossbar from key "testbar" is set with "Failed to get bossbar from key" + assert bossbar key of {_bar3} is set with "Failed to get key of bossbar" + assert {_bar3} is a keyed boss bar with "Failed to check if bar is a keyed bossbar" + assert title of {_bar3} is "testing3" with "Failed to set title of keyed bossbar" + assert progress of {_bar3} is 75% with "Failed to set progress of keyed bossbar" + assert style of {_bar3} is 20 notches with "Failed to set style of keyed bossbar" + assert color of {_bar3} is light green with "Failed to set color of keyed bossbar" + set {_bar4} to a boss bar: + set name of event-bossbar to "okay" + assert name of {_bar4} is "okay" with "Failed to set name of bossbar" + delete keyed boss bar in {_bar3} + + set {_bar4} to a boss bar with title "My Cool Bar" + assert title of {_bar4} is "My Cool Bar" with "Failed to set title of a bar in the expression" diff --git a/src/test/skript/tests/bukkit/enchantments/ExprStoredEnchantments.sk b/src/test/skript/tests/bukkit/enchantments/ExprStoredEnchantments.sk new file mode 100644 index 000000000..4fda92909 --- /dev/null +++ b/src/test/skript/tests/bukkit/enchantments/ExprStoredEnchantments.sk @@ -0,0 +1,19 @@ +test "stored enchantments": + set {_item} to 3 of minecraft:enchanted_book + + add efficiency 7 to stored enchantments of {_item} + add fire aspect 0 to stored enchantments of {_item} + assert size of stored enchantments of {_item} is 2 with "did not add 2 stored enchantments" + assert stored enchantments of {_item} contain fire aspect 0 with "fire aspect 0 couldn't be added" + + add sharpness to stored enchantments of {_item} + assert stored enchantments of {_item} contain sharpness 1 with "sharpness was not added" + + remove fire aspect 2 and efficiency from stored enchants of {_item} + assert stored enchantments of {_item} contain fire aspect 0 and sharpness 1 with "did not remove properly" + + clear stored enchantments of {_item} + assert stored enchantments of {_item} is not set with "did not clear enchantments properly" + + add sharpness and efficiency to stored enchantments of {_item} + assert stored enchantments of {_item} contain sharpness 1 and efficiency 1 with "did not convert properly" diff --git a/src/test/skript/tests/syntaxes/effects/EffTeleport.sk b/src/test/skript/tests/bukkit/entity/EffTeleport.sk similarity index 100% rename from src/test/skript/tests/syntaxes/effects/EffTeleport.sk rename to src/test/skript/tests/bukkit/entity/EffTeleport.sk diff --git a/src/test/skript/tests/bukkit/item/book/ExprBookPages.sk b/src/test/skript/tests/bukkit/item/book/ExprBookPages.sk new file mode 100644 index 000000000..09a8eb36a --- /dev/null +++ b/src/test/skript/tests/bukkit/item/book/ExprBookPages.sk @@ -0,0 +1,22 @@ +test "ExprBookPages": + set {_i} to a written book + + # all pages + assert {_i}'s book pages are not set with "non-existent book pages failed" + set pages of {_i} to "&aPages testing" and "test 2" + assert size of {_i}'s book pages is 2 with "failed to match exact pages size" + assert page 2 of {_i} is "test 2" with "failed to compare page 2" + add "test 3" to the pages of {_i} + assert page 3 of {_i} is "test 3" with "failed to compare page 3" + clear pages of {_i} + assert {_i}'s book pages are not set with "non-existent book pages failed 2" + + # specific pages + set page 3 of {_i} to "test 3" + assert page 3 of {_i} is "test 3" with "failed to set page 3" + assert size of {_i}'s book pages is 3 with "failed to add correct number of pages" + delete page 2 of {_i} + # page 3 becomes page 2 + assert page 2 of {_i} is "test 3" with "failed to remove page 2" + reset page 2 of {_i} + assert page 2 of {_i} is "" with "failed to reset page 2" diff --git a/src/test/skript/tests/bukkit/potion module.sk b/src/test/skript/tests/bukkit/potion module.sk index c0f726ed5..66b505a5d 100644 --- a/src/test/skript/tests/bukkit/potion module.sk +++ b/src/test/skript/tests/bukkit/potion module.sk @@ -151,7 +151,7 @@ test "potion effects of entities/items": # test invalid usage parse: clear the hidden potion effects of {_holders::1}'s tool - assert last parse logs is "Items (such as potions or stews) do not have hidden effects" with "hidden potion effects of item did not error" + assert last parse logs is "Only living entities have hidden effects" with "hidden potion effects of item did not error" # cleanup delete the entity within {_holders::1} @@ -227,7 +227,7 @@ test "specific potion effect of entities/items": # test invalid usage parse: clear the hidden speed effects of {_entity}'s tool - assert last parse logs is "Items (such as potions or stews) do not have hidden effects" with "hidden speed effects of item did not error" + assert last parse logs is "Only living entities have hidden effects" with "hidden speed effects of item did not error" # cleanup delete the entity within {_entity} diff --git a/src/test/skript/tests/bukkit/text/ExprResolvedComponent.sk b/src/test/skript/tests/bukkit/text/ExprResolvedComponent.sk new file mode 100644 index 000000000..fc066a395 --- /dev/null +++ b/src/test/skript/tests/bukkit/text/ExprResolvedComponent.sk @@ -0,0 +1,8 @@ +test "ExprResolvedComponent": + spawn a pig at event-location: + set {_entity} to event-entity + + set {_message} to formatted "" resolved for {_entity} + assert "%{_message}%" ends with "" with "Selector component did not resolve (got: '%{_message}%')" + + delete the entity within {_entity} diff --git a/src/test/skript/tests/bukkit/TextModule.sk b/src/test/skript/tests/bukkit/text/TextModule.sk similarity index 100% rename from src/test/skript/tests/bukkit/TextModule.sk rename to src/test/skript/tests/bukkit/text/TextModule.sk diff --git a/src/test/skript/tests/regressions/5750-aliases-structure-classcastexception.sk b/src/test/skript/tests/regressions/5750-aliases-structure-classcastexception.sk new file mode 100644 index 000000000..9118a9c3d --- /dev/null +++ b/src/test/skript/tests/regressions/5750-aliases-structure-classcastexception.sk @@ -0,0 +1,9 @@ +parse: + results: {aliases structure class cast exception::*} + code: + aliases: + {my custom variation}: + abc = stone + +test "5750-aliases structure class cast exception": + assert {aliases structure class cast exception::*} isn't set diff --git a/src/test/skript/tests/regressions/5871-aliases-structure-oome.sk b/src/test/skript/tests/regressions/5871-aliases-structure-oome.sk new file mode 100644 index 000000000..288c99d55 --- /dev/null +++ b/src/test/skript/tests/regressions/5871-aliases-structure-oome.sk @@ -0,0 +1,9 @@ +parse: + results: {aliases structure oome::*} + code: + aliases: + {null} = null + +test "5871-aliases structure oome": + assert {aliases structure oome::*} contains "Minecraft id null is not valid" + assert {aliases structure oome::*} contains "The variation {null} has not been defined before" diff --git a/src/test/skript/tests/regressions/5923-inventory-name-array-store-exception.sk b/src/test/skript/tests/regressions/5923-inventory-name-array-store-exception.sk new file mode 100644 index 000000000..cfeb12b7b --- /dev/null +++ b/src/test/skript/tests/regressions/5923-inventory-name-array-store-exception.sk @@ -0,0 +1,2 @@ +test "5923 inventory name array store exception": + set {_inv} to shulker box inventory named {_} diff --git a/src/test/skript/tests/syntaxes/conditions/CondLeashed.sk b/src/test/skript/tests/syntaxes/conditions/CondLeashed.sk new file mode 100644 index 000000000..e1daf4953 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondLeashed.sk @@ -0,0 +1,23 @@ +test "is leashed condition": + spawn a wolf at test-location + set {_wolf} to last spawned entity + assert {_wolf} is not leashed with "wolf should not be leashed initially" + assert {_wolf} isn't leashed with "wolf should not be leashed initially (alternate syntax)" + clear entity within {_wolf} + + spawn an armor stand at test-location + set {_stand} to last spawned entity + assert {_stand} is not leashed with "non-leashable entity should return false for is leashed" + clear entity within {_stand} + + spawn a wolf at test-location + set {_leash_holder} to last spawned entity + spawn a wolf at test-location + set {_wolf} to last spawned entity + assert {_wolf} is not leashed with "wolf should not be leashed initially" + leash {_wolf} to {_leash_holder} + assert {_wolf} is leashed with "wolf should be leashed after leashing" + unleash {_wolf} + assert {_wolf} is not leashed with "wolf should not be leashed after unleashing" + clear entity within {_wolf} + clear entity within {_leash_holder} 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 000000000..24c4eacaf --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffLeash.sk @@ -0,0 +1,36 @@ +test "leash effect": + spawn a wolf at test-location + set {_leash_holder} to last spawned entity + spawn a wolf at test-location + set {_wolf} to last spawned entity + assert {_wolf} is not leashed with "wolf should not be leashed initially" + leash {_wolf} to {_leash_holder} + assert {_wolf} is leashed with "wolf should be leashed after leash effect" + assert leash holder of {_wolf} is {_leash_holder} with "wolf should be leashed to the correct entity" + unleash {_wolf} + assert {_wolf} is not leashed with "wolf should be unleashed after unleash effect" + clear entity within {_wolf} + clear entity within {_leash_holder} + + spawn a wolf at test-location + set {_leash_holder} to last spawned entity + spawn 3 wolves at test-location + assert size of all wolves >= 4 with "at least 4 wolves should be spawned" + leash all wolves to {_leash_holder} + loop all wolves: + if loop-entity is not {_leash_holder}: + assert loop-entity is leashed with "all wolves (except holder) should be leashed" + assert leash holder of loop-entity is {_leash_holder} with "all wolves should be leashed to the correct entity" + unleash all wolves + loop all wolves: + assert loop-entity is not leashed with "all wolves should be unleashed" + clear all entities + + # Test leashing entity to itself + spawn a wolf at test-location + set {_self} to last spawned entity + leash {_self} to {_self} + # Check if self-leashing is supported by the Leashable API + if {_self} is leashed: + assert leash holder of {_self} is {_self} with "if self-leashing works, holder should be itself" + clear entity within {_self} diff --git a/src/test/skript/tests/syntaxes/effects/EffTree.sk b/src/test/skript/tests/syntaxes/effects/EffTree.sk new file mode 100644 index 000000000..a53271973 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffTree.sk @@ -0,0 +1,36 @@ +function cleanUpTree(loc: location): + set {_l1} to {_loc} ~ vector(7,20,7) + set {_l2} to {_loc} ~ vector(-7,-1,-7) + loop blocks within {_l1} and {_l2}: + if any: + loop-block is tagged as block tag "minecraft:mineable/axe" + loop-block is tagged as block tag "minecraft:leaves" + loop-block is creaking heart + loop-block is dirt + then: + set loop-block to air + +test "grow a tree": + set {_loc} to event-location + cleanUpTree({_loc}) # Pre cleanup to make sure we can grow a tree here + set block at {_loc} to dirt # Make sure our tree can grow here + + # Attempt to grow an oak tree + grow oak tree above {_loc} + set {_verify} to block 2 above {_loc} + assert {_verify} is oak log with "The oak tree should have grown" + cleanUpTree({_loc}) + + # Attempt to grow a huge mushroom + set block at {_loc} to dirt # Make sure our tree can grow here + grow huge mushroom above {_loc} + set {_verify} to block 2 above {_loc} + assert {_verify} is mushroom stem with "The huge mushroom should have grown" + cleanUpTree({_loc}) + + # Attempt to grow a pale oak tree + set block at {_loc} to dirt # Make sure our tree can grow here + grow pale oak tree above {_loc} + set {_verify} to block 2 above {_loc} + assert {_verify} is pale oak log with "The pale oak tree should have grown" + cleanUpTree({_loc}) diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk b/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk deleted file mode 100644 index 5bc9fb63a..000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk +++ /dev/null @@ -1,6 +0,0 @@ -test "book pages": - set {_i} to a written book - assert {_i}'s book pages is not set with "non-existent book pages failed 2" - set pages of {_i} to "&aPages testing" and "test 2" - assert size of {_i}'s book pages is 2 with "failed to match exact pages size" - assert page 2 of {_i} is "test 2" with "failed to compare page 2" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprElement.sk b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk index 774704187..76906268a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprElement.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk @@ -25,6 +25,13 @@ test "ExprElement": assert elements from 1 to 1 of {_list::*} is "foo" with "Incorrect elements from 1 to 1" assert (first element out of (5 and 6)) + 5 is 10 with "Incorrect return type" + assert size of 10 random distinct elements of {_list::*} is 3 + assert size of 10 random elements of {_list::*} is 10 + + loop 10 times: + assert 3 random distinct elements of {_list::*} contains {_list::*} + assert {_list::*} contains 10 random elements of {_list::*} + test "keyed ExprElement": set {_list::a} to "foo" set {_list::b} to "bar" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLeashHolder.sk b/src/test/skript/tests/syntaxes/expressions/ExprLeashHolder.sk new file mode 100644 index 000000000..c860ac115 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLeashHolder.sk @@ -0,0 +1,20 @@ +test "leash holder expression": + spawn a wolf at test-location + set {_leash_holder} to last spawned entity + spawn a wolf at test-location + set {_wolf} to last spawned entity + assert {_wolf} is not leashed with "wolf should not be leashed initially" + assert leash holder of {_wolf} is not set with "leash holder should not be set initially" + leash {_wolf} to {_leash_holder} + assert {_wolf} is leashed with "wolf should be leashed after leashing" + assert leash holder of {_wolf} is set with "leash holder should be set after leashing" + assert leash holder of {_wolf} is {_leash_holder} with "leash holder should be the entity it was leashed to" + unleash {_wolf} + assert leash holder of {_wolf} is not set with "leash holder should not be set after unleashing" + clear entity within {_wolf} + clear entity within {_leash_holder} + + spawn an armor stand at test-location + set {_stand} to last spawned entity + assert leash holder of {_stand} is not set with "non-leashable entity should return null for leash holder" + clear entity within {_stand} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprReplace.sk b/src/test/skript/tests/syntaxes/expressions/ExprReplace.sk new file mode 100644 index 000000000..a89b81c1c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprReplace.sk @@ -0,0 +1,35 @@ +test "replace expression": + + assert "hello this is a test" where "hello" is replaced with "goodbye" is "goodbye this is a test" with "replace" + + + assert "hello this is a test" where regex pattern "[el]" is replaced with "!" is "h!!!o this is a t!st" with "regex replace pattern 1" + + assert "hello this is a test" where "[el]" is replaced with "!" using regex is "h!!!o this is a t!st" with "regex replace pattern 2" + + + set {_test::*} to "hello", "this", "is", "a", "test" + + assert {_test::*} where "hello", "tes" are replaced with "!" is ("!", "this", "is", "a", "!t") with "replace list" + + assert {_test::*} where regex "[el]" is replaced with "!" is "h!!!o", "this", "is", "a", "t!st" with "regex replace list" + + + assert "aaaaAAAAbbbbBBBB" where "a" is replaced with "!" with case sensitivity is "!!!!AAAAbbbbBBBB" with "replace case sensitivity" + + + assert "test hello test hello this is a test" where first instance of "hello" is replaced with "!" is "test ! test hello this is a test" with "replace first" + + assert "test hello test hello this is a test" where all instances of "hello" are replaced with "!" is "test ! test ! this is a test" with "replace all" + + + assert "abc abcd" where first instance of regex "[ac]" is replaced with "!" is "!bc abcd" with "regex replace first" + + assert "abc abcd" where first instance of regex "[ac]", "[acd]" is replaced with "!" is "!b! abcd" with "regex replace multi first" + + + set {_test::*} to "hello", "this", "is", "a", "test" + + assert {_test::*} where first instance of "hel", "this" are replaced with "!" is ("!lo", "!", "is", "a", "test") with "first replace multi list" + + assert {_test::*} where first instances of regex patterns "[he]", "l" are replaced with "!" is ("!e!lo", "t!is", "is", "a", "t!st") with "first regex replace multi list" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprSkullTexture.sk b/src/test/skript/tests/syntaxes/expressions/ExprSkullTexture.sk new file mode 100644 index 000000000..81945125a --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprSkullTexture.sk @@ -0,0 +1,6 @@ +test "skull texture": + set {_head} to player head + set skull texture of {_head} to "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTM4NmRmZDc0Y2JhZmJkMWRiZTQ3OWY1ZTAzNzRjMDliZjJlYjRlMzg2NjExZmM0ZmM2OTlmMDJlY2E0ZGQyYyJ9fX0=" + assert skull texture of {_head} is "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTM4NmRmZDc0Y2JhZmJkMWRiZTQ3OWY1ZTAzNzRjMDliZjJlYjRlMzg2NjExZmM0ZmM2OTlmMDJlY2E0ZGQyYyJ9fX0=" with "skull texture should be set" + reset skull texture of {_head} + assert skull texture of {_head} is not set with "skull texture should be reset" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageAmount.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageAmount.sk index 845652e91..b6c1dd8e7 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageAmount.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageAmount.sk @@ -39,9 +39,11 @@ test "worldborder damage amount": add NaN value to worldborder damage amount of {_border} assert last caught runtime errors contains "NaN is not a valid world border damage amount" with "Adding NaN damage value did not throw error" assert worldborder damage amount of {_border} is 1.5 with "Adding NaN value to worldborder damage amount changed the damage amount" - add infinity value to worldborder damage amount of {_border} + catch runtime errors: + add infinity value to worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Adding infinity to worldborder damage amount changed the damage amount" - add -infinity value to worldborder damage amount of {_border} + catch runtime errors: + add -infinity value to worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Adding -infinity to worldborder damage amount changed the damage amount" # remove tests @@ -55,11 +57,14 @@ test "worldborder damage amount": assert worldborder damage amount of {_border} is 1.5 with "Failed removing negative integer from damage amount" remove {_None} from worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Removing None from worldborder damage amount changed the damage amount" - remove NaN value from worldborder damage amount of {_border} + catch runtime errors: + remove NaN value from worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Removing NaN value from worldborder damage amount changed the damage amount" - remove infinity value from worldborder damage amount of {_border} + catch runtime errors: + remove infinity value from worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Removing infinity from worldborder damage amount changed the damage amount" - remove -infinity value from worldborder damage amount of {_border} + catch runtime errors: + remove -infinity value from worldborder damage amount of {_border} assert worldborder damage amount of {_border} is 1.5 with "Removing -infinity from worldborder damage amount changed the damage amount" set worldborder damage amount of {_border} to -10 diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageBuffer.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageBuffer.sk index 779a731e8..bf6f1f370 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageBuffer.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageBuffer.sk @@ -1,3 +1,5 @@ +using error catching + test "worldborder damage buffer": set {_border} to worldborder of world("world") assert worldborder damage buffer of {_border} is 5 with "Default worldborder damage buffer is not 5" @@ -9,11 +11,14 @@ test "worldborder damage buffer": assert worldborder damage buffer of {_border} is 1.5 with "Failed to set worldborder damage buffer to a float" set worldborder damage buffer of {_border} to {_None} assert worldborder damage buffer of {_border} is 1.5 with "Setting worldborder damage buffer to None changed the damage" - set worldborder damage buffer of {_border} to NaN value + catch runtime errors: + set worldborder damage buffer of {_border} to NaN value assert worldborder damage buffer of {_border} is 1.5 with "Setting worldborder damage buffer to NaN changed the damage" - set worldborder damage buffer of {_border} to infinity value + catch runtime errors: + set worldborder damage buffer of {_border} to infinity value assert worldborder damage buffer of {_border} is infinity value with "Failed to set worldborder damage buffer to infinity" - set worldborder damage buffer of {_border} to -infinity value + catch runtime errors: + set worldborder damage buffer of {_border} to -infinity value assert worldborder damage buffer of {_border} is 0 with "Setting worldborder damage buffer to -infinity did not stop at 0" set worldborder damage buffer of {_border} to 1.5 @@ -28,12 +33,15 @@ test "worldborder damage buffer": assert worldborder damage buffer of {_border} is 1.5 with "Failed adding negative integer to damage buffer" add {_None} to worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 1.5 with "Adding None to worldborder damage buffer changed the damage buffer" - add NaN value to worldborder damage buffer of {_border} + catch runtime errors: + add NaN value to worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 1.5 with "Adding NaN to worldborder damage buffer changed the damage buffer" - add infinity value to worldborder damage buffer of {_border} + catch runtime errors: + add infinity value to worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is infinity value with "Failed adding infinity to damage buffer" set worldborder damage buffer of {_border} to 1.5 - add -infinity value to worldborder damage buffer of {_border} + catch runtime errors: + add -infinity value to worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 0 with "Adding -infinity to worldborder damage buffer did not stop at 0" set worldborder damage buffer of {_border} to 1.5 @@ -48,12 +56,15 @@ test "worldborder damage buffer": assert worldborder damage buffer of {_border} is 1.5 with "Failed removing negative integer from damage buffer" remove {_None} from worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 1.5 with "Removing None from worldborder damage buffer changed the damage buffer" - remove NaN value from worldborder damage buffer of {_border} + catch runtime errors: + remove NaN value from worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 1.5 with "Removing NaN from worldborder damage buffer changed the damage buffer" - remove infinity value from worldborder damage buffer of {_border} + catch runtime errors: + remove infinity value from worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is 0 with "Removing infinity from worldborder damage buffer did not stop at 0" set worldborder damage buffer of {_border} to 1.5 - remove -infinity value from worldborder damage buffer of {_border} + catch runtime errors: + remove -infinity value from worldborder damage buffer of {_border} assert worldborder damage buffer of {_border} is infinity value with "Failed removing -infinity from damage buffer" set worldborder damage buffer of {_border} to -10 diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderSize.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderSize.sk index 257c0a83f..92ae84a89 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderSize.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderSize.sk @@ -1,3 +1,5 @@ +using error catching + test "worldborder size": set {_max} to 59999968 @@ -18,7 +20,7 @@ test "worldborder size": set worldborder radius of {_border} to 10 assert worldborder radius of {_border} is 10 with "Worldborder radius failed to set" assert worldborder diameter of {_border} is 20 with "Worldborder diameter is not double worldborder radius" - + # set tests (diameter) set worldborder size of {_border} to 1 assert worldborder size of {_border} is 1 with "Failed to set worldborder size to an integer" @@ -26,11 +28,14 @@ test "worldborder size": assert worldborder size of {_border} is 1.5 with "Failed to set worldborder size to a float" set worldborder size of {_border} to {_None} assert worldborder size of {_border} is 1.5 with "Setting worldborder size to None changed the size" - set worldborder size of {_border} to NaN value + catch runtime errors: + set worldborder size of {_border} to NaN value assert worldborder size of {_border} is 1.5 with "Setting worldborder size to NaN value changed the size" - set worldborder size of {_border} to infinity value + catch runtime errors: + set worldborder size of {_border} to infinity value assert worldborder size of {_border} is {_max} with "Setting worldborder size to infinity went past %{_max}%" - set worldborder size of {_border} to -infinity value + catch runtime errors: + set worldborder size of {_border} to -infinity value assert worldborder size of {_border} is 1 with "Setting worldborder size to -infinity went under 1" set worldborder size of {_border} to 1.5 @@ -45,11 +50,14 @@ test "worldborder size": assert worldborder size of {_border} is 1.5 with "Failed adding negative integer to size" add {_None} to worldborder size of {_border} assert worldborder size of {_border} is 1.5 with "Adding None to worldborder size changed the size" - add NaN value to worldborder size of {_border} + catch runtime errors: + add NaN value to worldborder size of {_border} assert worldborder size of {_border} is 1.5 with "Adding NaN value to worldborder size changed the size" - add infinity value to worldborder size of {_border} + catch runtime errors: + add infinity value to worldborder size of {_border} assert worldborder size of {_border} is {_max} with "Adding infinity to worldborder size went past %{_max}%" - add -infinity value to worldborder size of {_border} + catch runtime errors: + add -infinity value to worldborder size of {_border} assert worldborder size of {_border} is 1 with "Adding -infinity to worldborder size went under 1" set worldborder size of {_border} to 1.5 @@ -64,13 +72,16 @@ test "worldborder size": assert worldborder size of {_border} is 1.5 with "Failed removing integer from size" remove {_None} from worldborder size of {_border} assert worldborder size of {_border} is 1.5 with "Removing None from worldborder size changed the size" - remove NaN value from worldborder size of {_border} + catch runtime errors: + remove NaN value from worldborder size of {_border} assert worldborder size of {_border} is 1.5 with "Removing NaN value from worldborder size changed the size" - remove infinity value from worldborder size of {_border} + catch runtime errors: + remove infinity value from worldborder size of {_border} assert worldborder size of {_border} is 1 with "Removing infinity from worldborder size went below 1" - remove -infinity value from worldborder size of {_border} + catch runtime errors: + remove -infinity value from worldborder size of {_border} assert worldborder size of {_border} is {_max} with "Removing -infinity from worldborder size went past %{_max}%" - + reset worldborder size of {_border} assert worldborder size of {_border} is {_max} with "The reset diameter of the world's worldborder isn't %{_max}%" @@ -81,11 +92,14 @@ test "worldborder size": assert worldborder radius of {_border} is 1.5 with "Failed to set worldborder radius to a float" set worldborder radius of {_border} to {_None} assert worldborder radius of {_border} is 1.5 with "Setting worldborder radius to None changed the radius" - set worldborder radius of {_border} to NaN value + catch runtime errors: + set worldborder radius of {_border} to NaN value assert worldborder radius of {_border} is 1.5 with "Setting worldborder radius to NaN value changed the radius" - set worldborder radius of {_border} to infinity value + catch runtime errors: + set worldborder radius of {_border} to infinity value assert worldborder radius of {_border} is {_max}/2 with "Setting worldborder radius to infinity went past %{_max}/2%" - set worldborder radius of {_border} to -infinity value + catch runtime errors: + set worldborder radius of {_border} to -infinity value assert worldborder radius of {_border} is 0.5 with "Setting worldborder radius to -infinity went under 0.5" set worldborder radius of {_border} to 1.5 @@ -100,11 +114,14 @@ test "worldborder size": assert worldborder radius of {_border} is 1.5 with "Failed adding negative integer to radius" add {_None} to worldborder radius of {_border} assert worldborder radius of {_border} is 1.5 with "Adding None to worldborder radius changed the radius" - add NaN value to worldborder radius of {_border} + catch runtime errors: + add NaN value to worldborder radius of {_border} assert worldborder radius of {_border} is 1.5 with "Adding NaN value to worldborder radius changed the radius" - add infinity value to worldborder radius of {_border} + catch runtime errors: + add infinity value to worldborder radius of {_border} assert worldborder radius of {_border} is {_max}/2 with "Adding infinity to worldborder radius went past %{_max}/2%" - add -infinity value to worldborder radius of {_border} + catch runtime errors: + add -infinity value to worldborder radius of {_border} assert worldborder radius of {_border} is 0.5 with "Adding -infinity to worldborder radius went under 0.5" set worldborder radius of {_border} to 1.5 @@ -119,11 +136,14 @@ test "worldborder size": assert worldborder radius of {_border} is 1.5 with "Failed removing integer from radius" remove {_None} from worldborder radius of {_border} assert worldborder radius of {_border} is 1.5 with "Removing None from worldborder radius changed the radius" - remove NaN value from worldborder radius of {_border} + catch runtime errors: + remove NaN value from worldborder radius of {_border} assert worldborder radius of {_border} is 1.5 with "Removing NaN value from worldborder radius changed the radius" - remove infinity value from worldborder radius of {_border} + catch runtime errors: + remove infinity value from worldborder radius of {_border} assert worldborder radius of {_border} is 0.5 with "Removing infinity from worldborder radius went below 0.5" - remove -infinity value from worldborder radius of {_border} + catch runtime errors: + remove -infinity value from worldborder radius of {_border} assert worldborder radius of {_border} is {_max}/2 with "Removing -infinity from worldborder radius went past %{_max}/2%" reset worldborder radius of {_border} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningDistance.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningDistance.sk index a9052f652..0fe2a0d9c 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningDistance.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningDistance.sk @@ -1,3 +1,5 @@ +using error catching + test "worldborder warning distance": set {_max} to max integer value set {_border} to worldborder of world("world") @@ -9,11 +11,14 @@ test "worldborder warning distance": assert worldborder warning distance of {_border} is 1 with "Failed to set worldborder warning distance" set worldborder warning distance of {_border} to {_None} assert worldborder warning distance of {_border} is 1 with "Setting worldborder warning distance to None changed the warning distance" - set worldborder warning distance of {_border} to NaN value + catch runtime errors: + set worldborder warning distance of {_border} to NaN value assert worldborder warning distance of {_border} is 1 with "Setting worldborder warning distance to NaN changed the warning distance" - set worldborder warning distance of {_border} to infinity value + catch runtime errors: + set worldborder warning distance of {_border} to infinity value assert worldborder warning distance of {_border} is {_max} with "Failed to set worldborder warning distance to infinity" - set worldborder warning distance of {_border} to -infinity value + catch runtime errors: + set worldborder warning distance of {_border} to -infinity value assert worldborder warning distance of {_border} is 0 with "Setting worldborder warning distance to -infinity did not stop at 0" set worldborder warning distance of {_border} to 1 @@ -24,11 +29,14 @@ test "worldborder warning distance": assert worldborder warning distance of {_border} is 1 with "Failed adding negative integer to warning distance" add {_None} to worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 1 with "Adding None to worldborder warning distance changed the warning distance" - add NaN value to worldborder warning distance of {_border} + catch runtime errors: + add NaN value to worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 1 with "Adding NaN to worldborder warning distance changed the warning distance" - add -infinity value to worldborder warning distance of {_border} + catch runtime errors: + add -infinity value to worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 0 with "Adding -infinity to worldborder warning distance did not stop at 0" - add infinity value to worldborder warning distance of {_border} + catch runtime errors: + add infinity value to worldborder warning distance of {_border} assert worldborder warning distance of {_border} is {_max} with "Failed adding infinity to warning distance" set worldborder warning distance of {_border} to 1 @@ -39,11 +47,14 @@ test "worldborder warning distance": assert worldborder warning distance of {_border} is 1 with "Failed removing negative integer from warning distance" remove {_None} from worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 1 with "Removing None from worldborder warning distance changed the warning distance" - remove NaN value from worldborder warning distance of {_border} + catch runtime errors: + remove NaN value from worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 1 with "Removing NaN from worldborder warning distance changed the warning distance" - remove infinity value from worldborder warning distance of {_border} + catch runtime errors: + remove infinity value from worldborder warning distance of {_border} assert worldborder warning distance of {_border} is 0 with "Removing infinity from worldborder warning distance did not stop at 0" - remove -infinity value from worldborder warning distance of {_border} + catch runtime errors: + remove -infinity value from worldborder warning distance of {_border} assert worldborder warning distance of {_border} is {_max} with "Failed removing -infinity from warning distance" set worldborder warning distance of {_border} to -10 diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningTime.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningTime.sk index 8dcf066c1..2ce87c6a1 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningTime.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldBorderWarningTime.sk @@ -1,6 +1,8 @@ test "worldborder warning time": # cannot be perfect max due to internal handling to convert between ticks and seconds set {_max} to 2147483647 ticks - 0.35 seconds + if running minecraft "1.21.11": + set {_max} to 2147483647 ticks set {_border} to worldborder of world("world") assert worldborder warning time of {_border} is 15 seconds with "Default warning time is not 15 seconds" set worldborder warning time of {_border} to 10 seconds @@ -19,3 +21,4 @@ test "worldborder warning time": assert worldborder warning time of {_border} is {_max} with "Addition with numbers larger than Integer Max failed" reset worldborder warning time of {_border} assert worldborder warning time of {_border} is 15 seconds with "Failed to reset warning time" +