diff --git a/.github/workflows/archive-docs.yml b/.github/workflows/archive-docs.yml index e183f748d78..c31092b63b0 100644 --- a/.github/workflows/archive-docs.yml +++ b/.github/workflows/archive-docs.yml @@ -8,7 +8,6 @@ on: jobs: archive-docs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - needs: release-docs runs-on: ubuntu-latest steps: - name: Configure workflow diff --git a/.github/workflows/java-11-builds.yml b/.github/workflows/java-11-builds.yml deleted file mode 100644 index 443aa925d0c..00000000000 --- a/.github/workflows/java-11-builds.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Java 11 CI (MC 1.13-1.16) - -on: - push: - branches: - - master - - 'dev/**' - pull_request: - -jobs: - build: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build Skript and run test scripts - run: ./gradlew clean skriptTestJava11 - - name: Upload Nightly Build - uses: actions/upload-artifact@v4 - if: success() - with: - name: skript-nightly - path: build/libs/* diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 2ca6d6bde61..43d6458c946 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,4 +1,4 @@ -name: Java 17 CI (MC 1.17-1.20.4) +name: Java 17 CI (MC 1.19.4-1.20.4) on: push: diff --git a/.github/workflows/junit-11-builds.yml b/.github/workflows/junit-11-builds.yml deleted file mode 100644 index cdf75e4c43a..00000000000 --- a/.github/workflows/junit-11-builds.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: JUnit (MC 1.13-1.16) - -on: - push: - branches: - - master - - 'dev/**' - pull_request: - -jobs: - build: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build Skript and run JUnit - run: ./gradlew clean JUnitJava11 diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 238bd9b132e..7bbe8952eb3 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -1,4 +1,4 @@ -name: JUnit (MC 1.17-1.20.4) +name: JUnit (MC 1.19.4-1.20.4) on: push: diff --git a/README.md b/README.md index b4ab6c4e48f..bd1e1375345 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Skript requires **Spigot** to work. You heard it right, **CraftBukkit** does *no **Paper**, which is a fork of Spigot, is recommended; it is required for some parts of Skript to be available. -Skript supports only the **latest** patch versions of Minecraft 1.13+. -For example, this means that 1.16.5 is supported, but 1.16.4 is *not*. +Skript supports only the **latest** patch versions of Minecraft 1.19 and newer. +For example, this means that 1.19.4 is supported, but 1.19.3 is *not*. Testing with all old patch versions is not feasible for us. Minecraft 1.12 and earlier are not, and will not be supported. New Minecraft @@ -77,15 +77,14 @@ Skript has some tests written in Skript. Running them requires a Minecraft server, but our build script will create one for you. Running the tests is easy: ``` -./gradlew (quickTest|skriptTest|skriptTestJava11|skriptTestJava17|skriptTestJava21) +./gradlew (quickTest|skriptTest|skriptTestJava17|skriptTestJava21) ``` quickTest runs the test suite on newest supported server version. skriptTestJava21 (1.20.6+) runs the tests on Java 21 supported versions. -skriptTestJava17 (1.17-1.20.4) runs the tests on Java 17 supported versions. -skriptTestJava11 (1.13-1.16) runs the tests on Java 11 supported versions. +skriptTestJava17 (1.19.4-1.20.4) runs the tests on Java 17 supported versions. skriptTest runs the tests on all versions. -That is, it runs skriptTestJava11, skriptTestJava17, and skriptTestJava21. +That is, it runs skriptTestJava17, and skriptTestJava21. By running the tests, you agree to Mojang's End User License Agreement. diff --git a/build.gradle b/build.gradle index d6a60fa9e37..415ed5b5709 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.6-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -70,7 +70,7 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } -// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava11, skriptTest, quickTest. +// Excludes the tests for the build task. Should be using junit, junitJava17, skriptTest, quickTest. // We do not want tests to run for building. That's time consuming and annoying. Especially in development. test { exclude '**/*' @@ -233,11 +233,10 @@ void createTestTask(String name, String desc, String environments, int javaVersi def java21 = 21 def java17 = 17 -def java11 = 11 -def latestEnv = 'java21/paper-1.20.6.json' +def latestEnv = 'java21/paper-1.21.0.json' def latestJava = java21 -def oldestJava = java11 +def oldestJava = java17 def latestJUnitEnv = 'java17/paper-1.20.4.json' def latestJUnitJava = java17 @@ -260,14 +259,13 @@ int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Inte createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0) createTestTask('skriptTestJava21', 'Runs tests on all Java 21 environments.', environments + 'java21', java21, 0) createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', java17, 0) -createTestTask('skriptTestJava11', 'Runs tests on all Java 11 environments.', environments + 'java11', java11, 0) createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, 0, Modifiers.DEV_MODE, Modifiers.DEBUG) createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, -1, Modifiers.PROFILE) createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, 0, Modifiers.GEN_NIGHTLY_DOCS) createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, 0, Modifiers.GEN_RELEASE_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' - dependsOn skriptTestJava11, skriptTestJava17, skriptTestJava21 + dependsOn skriptTestJava17, skriptTestJava21 } createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestJUnitEnv, latestJUnitJava, 0, Modifiers.JUNIT) @@ -275,10 +273,9 @@ createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the late // However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) //createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', java17, 0, Modifiers.JUNIT) -createTestTask('JUnitJava11', 'Runs JUnit tests on all Java 11 environments.', environments + 'java11', java11, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' - dependsOn JUnitJava11, JUnitJava17//, JUnitJava21 + dependsOn JUnitJava17//, JUnitJava21 } // Build flavor configurations diff --git a/code-conventions.md b/code-conventions.md index 3bcf17c6c9f..c9b70f8d5a4 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -194,9 +194,8 @@ Your comments should look something like these: ## Language Features ### Compatibility -[//]: # (To be updated for 2.10 for Java 17) -* Contributions should maintain Java 11 source/binary compatibility, even though compiling Skript requires Java 21 - - Users must not need JRE newer than version 11 +* Contributions should maintain Java 17 source/binary compatibility, even though compiling Skript requires Java 21 + - Users must not need JRE newer than version 17 * Versions up to and including Java 21 should work too - Please avoid using unsafe reflection * It is recommended to make fields final, if they are effectively final diff --git a/gradle.properties b/gradle.properties index f78ae1766eb..befe897fdfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.7 +version=2.9.1 jarName=Skript.jar -testEnv=java21/paper-1.20.6 +testEnv=java21/paper-1.21.0 testEnvJavaVersion=21 diff --git a/skript-aliases b/skript-aliases index 9ea857f6b7d..16949c28e0d 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 9ea857f6b7dd1e4fc4a35a88149b9e463b537b06 +Subproject commit 16949c28e0d7bb25ea7c3479c3d6754ff3b5a3e6 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index d98a51a7763..3755525edb1 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -752,6 +752,8 @@ protected void afterErrors() { TestTracker.JUnitTestFailed(test, message); Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); }); + if (SkriptJUnitTest.class.isAssignableFrom(clazz)) + ((SkriptJUnitTest) clazz.getConstructor().newInstance()).cleanup(); SkriptJUnitTest.clearJUnitTest(); } } catch (IOException e) { @@ -759,6 +761,8 @@ protected void afterErrors() { } catch (ClassNotFoundException e) { // Should be the Skript test jar gradle task. assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found."; + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + Skript.exception(e, "Failed to initalize test JUnit classes."); } if (ignored > 0) Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!"); diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 5a3bb954cd4..ee715ce038e 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -170,6 +170,12 @@ public static String formatDate(final long timestamp) { } }); + /** + * Determines whether `on <event>` will be triggered by cancelled events or not. + */ + public static final Option listenCancelledByDefault = new Option<>("listen to cancelled events by default", false) + .optional(true); + /** * Maximum number of digits to display after the period for floats and doubles diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 56bffc24f9a..111ffc5adcd 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -25,6 +25,7 @@ import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.entity.EntityData; +import org.bukkit.entity.EntityType; import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; @@ -267,7 +268,7 @@ public static ItemType parseItemType(String s) { } String lc = s.toLowerCase(Locale.ENGLISH); - String of = Language.getSpaced("enchantments.of").toLowerCase(); + String of = Language.getSpaced("of").toLowerCase(); int c = -1; outer: while ((c = lc.indexOf(of, c + 1)) != -1) { ItemType t2 = t.clone(); @@ -431,7 +432,6 @@ private static void loadInternal() throws IOException { Path aliasesPath = zipFs.getPath("/", "aliases-english"); assert aliasesPath != null; loadDirectory(aliasesPath); - loadMissingAliases(); } } catch (URISyntaxException e) { assert false; @@ -445,6 +445,9 @@ private static void loadInternal() throws IOException { assert aliasesFolder != null; loadDirectory(aliasesFolder); } + + // generate aliases from item names for any missing items + loadMissingAliases(); // Update tracked item types for (Map.Entry entry : trackedTypes.entrySet()) { @@ -554,10 +557,16 @@ public static EntityData getRelatedEntity(ItemData data) { *

Item types provided by this method are updated when aliases are * reloaded. However, this also means they are tracked by aliases system * and NOT necessarily garbage-collected. + * + *

Relying on this method to create item types is not safe, + * as users can change aliases at any point. ItemTypes should instead be created + * via {@link Material}s, {@link org.bukkit.Tag}s, or any other manual method. + * * @param name Name of item to search from aliases. * @return An item. * @throws IllegalArgumentException When item is not found. */ + @Deprecated(forRemoval = true, since = "2.9.0") public static ItemType javaItemType(String name) { ItemType type = parseItemType(name); if (type == null) { diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index a3b8b215b75..cce78c4ef71 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -304,16 +304,19 @@ public void addAlias(AliasName name, String id, @Nullable Map ta } // Apply (NBT) tags to item stack - ItemStack stack = new ItemStack(material); + ItemStack stack = null; int itemFlags = 0; - if (tags != null) { - itemFlags = applyTags(stack, new HashMap<>(tags)); + if (material.isItem()) { + stack = new ItemStack(material); + if (tags != null) { + itemFlags = applyTags(stack, new HashMap<>(tags)); + } } // Parse block state to block values BlockValues blockValues = BlockCompat.INSTANCE.createBlockValues(material, blockStates, stack, itemFlags); - ItemData data = new ItemData(stack, blockValues); + ItemData data = stack != null ? new ItemData(stack, blockValues) : new ItemData(material, blockValues); data.isAlias = true; data.itemFlags = itemFlags; @@ -413,6 +416,7 @@ public EntityData getRelatedEntity(ItemData item) { public void clearAliases() { aliases.clear(); + materials.clear(); variations.clear(); aliasesMap.clear(); } diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 32323c57e6f..f18812aabb8 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -37,7 +37,7 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.NotSerializableException; @@ -91,11 +91,6 @@ public static class OldItemData { @Deprecated public static final boolean itemDataValues = false; - /** - * ItemStack, which is used for everything but serialization. - */ - transient ItemStack stack; - /** * Type of the item as Bukkit material. Serialized manually. */ @@ -105,14 +100,18 @@ public static class OldItemData { * If this represents all possible items. */ boolean isAnything; - + + /** + * ItemStack, which is used for everything but serialization. + */ + transient @Nullable ItemStack stack; + /** * When this ItemData represents a block, this contains information to * allow comparing it against other blocks. */ - @Nullable - BlockValues blockValues; - + @Nullable BlockValues blockValues; + /** * Whether this represents an item (that definitely cannot have * block states) or a block, which might have them. @@ -140,9 +139,10 @@ public static class OldItemData { public ItemData(Material type, @Nullable String tags) { this.type = type; - - this.stack = new ItemStack(type); - this.blockValues = BlockCompat.INSTANCE.getBlockValues(stack); + + if (type.isItem()) + this.stack = new ItemStack(type); + this.blockValues = BlockCompat.INSTANCE.getBlockValues(type); if (tags != null) { applyTags(tags); } @@ -150,8 +150,9 @@ public ItemData(Material type, @Nullable String tags) { public ItemData(Material type, int amount) { this.type = type; - this.stack = new ItemStack(type, Math.abs(amount)); - this.blockValues = BlockCompat.INSTANCE.getBlockValues(stack); + if (type.isItem()) + this.stack = new ItemStack(type, Math.abs(amount)); + this.blockValues = BlockCompat.INSTANCE.getBlockValues(type); } public ItemData(Material type) { @@ -159,13 +160,18 @@ public ItemData(Material type) { } public ItemData(ItemData data) { - this.stack = data.stack.clone(); + this.stack = data.stack != null ? data.stack.clone() : null; this.type = data.type; this.blockValues = data.blockValues; this.isAlias = data.isAlias; this.plain = data.plain; this.itemFlags = data.itemFlags; } + + public ItemData(Material material, @Nullable BlockValues values) { + this.type = material; + this.blockValues = values; + } public ItemData(ItemStack stack, @Nullable BlockValues values) { this.stack = stack; @@ -200,7 +206,8 @@ public ItemData(BlockState blockState) { public ItemData(BlockData blockData) { this.type = blockData.getMaterial(); - this.stack = new ItemStack(type); + if (type.isItem()) + this.stack = new ItemStack(type); this.blockValues = BlockCompat.INSTANCE.getBlockValues(blockData); } @@ -227,13 +234,12 @@ public boolean isOfType(@Nullable ItemStack item) { if (type != item.getType()) return false; // Obvious mismatch - if (itemFlags != 0) { // Either stack has tags (or durability) + if (stack != null && itemFlags != 0) { // Either stack has tags (or durability) if (ItemUtils.getDamage(stack) != ItemUtils.getDamage(item)) return false; // On 1.12 and below, damage is not in meta if (stack.hasItemMeta() == item.hasItemMeta()) // Compare ItemMeta as in isSimilar() of ItemStack - return stack.hasItemMeta() ? itemFactory.equals(stack.getItemMeta(), item.getItemMeta()) : true; - else - return false; + return !stack.hasItemMeta() || itemFactory.equals(stack.getItemMeta(), item.getItemMeta()); + return false; } return true; } @@ -249,7 +255,7 @@ public String toString() { public String toString(final boolean debug, final boolean plural) { StringBuilder builder = new StringBuilder(Aliases.getMaterialName(this, plural)); - ItemMeta meta = stack.getItemMeta(); + ItemMeta meta = stack != null ? stack.getItemMeta() : null; if (meta != null && meta.hasDisplayName()) { builder.append(" ").append(m_named).append(" "); builder.append(meta.getDisplayName()); @@ -272,7 +278,7 @@ public boolean equals(final @Nullable Object obj) { return false; ItemData other = (ItemData) obj; - if (isAlias) { // This is alias, other item might not be + if (isAlias()) { // This is alias, other item might not be return other.matchAlias(this).isAtLeast(MatchQuality.SAME_ITEM); } else { // This is not alias, but other might be return matchAlias(other).isAtLeast(MatchQuality.SAME_ITEM); @@ -282,7 +288,7 @@ public boolean equals(final @Nullable Object obj) { @Override public int hashCode() { int hash = type.hashCode(); // Has collisions, but probably not too many of them - if (blockValues == null || (blockValues != null && blockValues.isDefault())) { + if (blockValues == null || blockValues.isDefault()) { hash = hash * 37 + 1; } return hash; @@ -351,7 +357,7 @@ public MatchQuality matchAlias(ItemData item) { } // See if we need to compare item metas (excluding durability) - if (quality.isAtLeast(MatchQuality.SAME_ITEM) && stack.hasItemMeta() || item.stack.hasItemMeta()) { // Item meta checks could lower this + if (quality.isAtLeast(MatchQuality.SAME_ITEM) && this.hasItemMeta() || item.hasItemMeta()) { // Item meta checks could lower this MatchQuality metaQuality = compareItemMetas(getItemMeta(), item.getItemMeta()); // If given item doesn't care about meta, promote to SAME_ITEM @@ -489,9 +495,13 @@ public ItemData intersection(final ItemData other) { * It is not a copy, so please be careful. * @return Item stack. */ - public ItemStack getStack() { + public @Nullable ItemStack getStack() { return stack; } + + private boolean hasItemMeta() { + return stack != null && stack.hasItemMeta(); + } @Override public ItemData clone() { @@ -508,7 +518,7 @@ public BlockValues getBlockValues() { } public ItemMeta getItemMeta() { - ItemMeta meta = stack.getItemMeta(); + ItemMeta meta = stack != null ? stack.getItemMeta() : null; if (meta == null) { // AIR has null item meta! meta = itemFactory.getItemMeta(Material.STONE); } @@ -517,6 +527,8 @@ public ItemMeta getItemMeta() { } public void setItemMeta(ItemMeta meta) { + if (stack == null) + return; stack.setItemMeta(meta); isAlias = false; // This is no longer exact alias plain = false; // This is no longer a plain item @@ -524,10 +536,14 @@ public void setItemMeta(ItemMeta meta) { } public int getDurability() { + if (stack == null) + return 0; // no damage? return ItemUtils.getDamage(stack); } public void setDurability(int durability) { + if (stack == null) + return; ItemUtils.setDamage(stack, durability); isAlias = false; // Change happened plain = false; // This is no longer a plain item @@ -567,7 +583,7 @@ public boolean matchPlain(ItemData other) { public Fields serialize() throws NotSerializableException { Fields fields = new Fields(this); // ItemStack is transient, will be ignored fields.putPrimitive("id", type.ordinal()); - fields.putObject("meta", stack.getItemMeta()); + fields.putObject("meta", stack != null ? stack.getItemMeta() : null); return fields; } @@ -579,8 +595,10 @@ public void deserialize(Fields fields) throws StreamCorruptedException, NotSeria ItemMeta meta = fields.getAndRemoveObject("meta", ItemMeta.class); // Initialize ItemStack - this.stack = new ItemStack(type); - stack.setItemMeta(meta); // Just set meta to it + if (meta != null && type.isItem()) { + this.stack = new ItemStack(type); + stack.setItemMeta(meta); // Just set meta to it + } fields.setFields(this); // Everything but ItemStack and Material } @@ -598,17 +616,17 @@ public void deserialize(Fields fields) throws StreamCorruptedException, NotSeria */ public ItemData aliasCopy() { ItemData data = new ItemData(); - data.stack = new ItemStack(type, 1); - - if (stack.hasItemMeta()) { - ItemMeta meta = stack.getItemMeta(); // Creates a copy - meta.setDisplayName(null); // Clear display name - if (!itemFactory.getItemMeta(type).equals(meta)) // there may be different tags (e.g. potions) - data.itemFlags |= ItemFlags.CHANGED_TAGS; - data.stack.setItemMeta(meta); + if (stack != null) { + data.stack = new ItemStack(type, 1); + if (stack.hasItemMeta()) { + ItemMeta meta = stack.getItemMeta(); // Creates a copy + meta.setDisplayName(null); // Clear display name + if (!itemFactory.getItemMeta(type).equals(meta)) // there may be different tags (e.g. potions) + data.itemFlags |= ItemFlags.CHANGED_TAGS; + data.stack.setItemMeta(meta); + } + ItemUtils.setDamage(data.stack, 0); // Set to undamaged } - ItemUtils.setDamage(data.stack, 0); // Set to undamaged - data.type = type; data.blockValues = blockValues; data.itemForm = itemForm; @@ -620,6 +638,8 @@ public ItemData aliasCopy() { * @param tags Tags in Mojang's JSON format. */ public void applyTags(String tags) { + if (stack == null) + return; BukkitUnsafe.modifyItemStack(stack, tags); itemFlags |= ItemFlags.CHANGED_TAGS; } diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 7341667fa91..d2a6fc85421 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -40,6 +40,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.Tag; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Skull; @@ -68,6 +69,7 @@ import java.util.Random; import java.util.RandomAccess; import java.util.Set; +import java.util.stream.Collectors; @ContainerType(ItemStack.class) public class ItemType implements Unit, Iterable, Container, YggdrasilExtendedSerializable { @@ -172,6 +174,18 @@ public ItemType(Material id) { add_(new ItemData(id)); } + public ItemType(Material... ids) { + for (Material id : ids) { + add_(new ItemData(id)); + } + } + + public ItemType(Tag tag) { + for (Material id : tag.getValues()) { + add_(new ItemData(id)); + } + } + public ItemType(Material id, String tags) { add_(new ItemData(id, tags)); } @@ -315,7 +329,7 @@ public boolean isOfType(Material id, @Nullable String tags) { public boolean isOfType(Material id) { // TODO avoid object creation - return isOfType(new ItemData(id, null)); + return isOfType(new ItemData(id, (String) null)); } /** @@ -343,7 +357,7 @@ public ItemType getBlock() { */ public boolean hasItem() { for (ItemData d : types) { - if (!d.type.isBlock()) + if (d.type.isItem()) return true; } return false; @@ -487,9 +501,13 @@ public boolean hasNext() { @Override public ItemStack next() { - if (!hasNext()) - throw new NoSuchElementException(); - ItemStack is = iter.next().getStack().clone(); + ItemStack is = null; + while (is == null) { + if (!hasNext()) + throw new NoSuchElementException(); + is = iter.next().getStack(); + } + is = is.clone(); is.setAmount(getAmount()); return is; } @@ -588,14 +606,33 @@ public ItemType clone() { * @see #removeFrom(ItemStack) * @see #removeFrom(List...) */ - public ItemStack getRandom() { - int numItems = types.size(); - int index = random.nextInt(numItems); - ItemStack is = types.get(index).getStack().clone(); + public @Nullable ItemStack getRandom() { + List datas = types.stream() + .filter(data -> data.stack != null) + .collect(Collectors.toList()); + if (datas.isEmpty()) + return null; + ItemStack is = datas.get(random.nextInt(datas.size())).getStack(); + assert is != null; // verified above + is = is.clone(); is.setAmount(getAmount()); return is; } + /** + * @return One random ItemStack or Material that this ItemType represents. + * A Material may only be returned for ItemStacks containing a Material where {@link Material#isItem()} is false. + */ + public Object getRandomStackOrMaterial() { + ItemData randomData = types.get(random.nextInt(types.size())); + ItemStack stack = randomData.getStack(); + if (stack == null) + return randomData.getType(); + stack = stack.clone(); + stack.setAmount(getAmount()); + return stack; + } + /** * Test whether this ItemType can be put into the given inventory completely. *

@@ -869,7 +906,9 @@ public final boolean removeFrom(boolean replaceWithNull, List... list */ public void addTo(final List list) { if (!isAll()) { - list.add(getItem().getRandom()); + ItemStack random = getItem().getRandom(); + if (random != null) + list.add(getItem().getRandom()); return; } for (final ItemStack is : getItem().getAll()) @@ -936,7 +975,9 @@ private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { public boolean addTo(final ItemStack[] buf) { if (!isAll()) { - return addTo(getItem().getRandom(), buf); + ItemStack random = getItem().getRandom(); + if (random != null) + return addTo(getItem().getRandom(), buf); } boolean ok = true; for (ItemStack is : getItem().getAll()) { @@ -1367,8 +1408,11 @@ public void clearItemMeta() { globalMeta = null; } + /** + * @return A random Material this ItemType represents. + */ public Material getMaterial() { - ItemData data = types.get(0); + ItemData data = types.get(random.nextInt(types.size())); if (data == null) throw new IllegalStateException("material not found"); return data.getType(); diff --git a/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java b/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java new file mode 100644 index 00000000000..fd596a324a4 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java @@ -0,0 +1,108 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.bukkitutil; + +import java.util.Locale; +import java.util.OptionalLong; + +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +import ch.njol.skript.effects.EffPlaySound; + +/** + * A utility interface to access the Player::playSound while also providing the same arguments to World::playSound + * Used in EffPlaySound. Separated due to static versioning. + */ +@FunctionalInterface +public interface AdventureSoundReceiver { + + void play( + @NotNull T receiver, @NotNull E emitter, @NotNull String sound, + @NotNull SoundCategory category, float volume, float pitch + ); + + static void play( + @NotNull AdventureEmitterSoundReceiver adventureLocationReceiver, + @NotNull AdventureEntitySoundReceiver adventureEmitterReceiver, + @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, + @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed + ) { + for (String sound : sounds) { + NamespacedKey key = null; + try { + Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); + key = enumSound.getKey(); + } catch (IllegalArgumentException alternative) { + sound = sound.toLowerCase(Locale.ENGLISH); + if (!EffPlaySound.KEY_PATTERN.matcher(sound).matches()) + continue; + try { + key = NamespacedKey.fromString(sound); + } catch (IllegalArgumentException argument) { + // The user input invalid characters + } + } + + if (key == null) + continue; + net.kyori.adventure.sound.Sound adventureSound = net.kyori.adventure.sound.Sound.sound() + .source(category) + .volume(volume) + .pitch(pitch) + .seed(seed) + .type(key) + .build(); + AdventureEmitterSoundReceiver.play(adventureLocationReceiver, adventureEmitterReceiver, receiver, adventureSound, emitter); + } + } + + @FunctionalInterface + public interface AdventureEmitterSoundReceiver { + void play( + @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, double x, double y, double z + ); + + static void play( + @NotNull AdventureEmitterSoundReceiver locationReceiver, + @NotNull AdventureEntitySoundReceiver emitterReceiver, + @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, @NotNull E emitter + ) { + if (emitter instanceof Location) { + Location location = (Location) emitter; + locationReceiver.play(receiver, sound, location.getX(), location.getY(), location.getZ()); + } else if (emitter instanceof Entity) { + Entity entity = (Entity) emitter; + emitterReceiver.play(receiver, sound, entity); + } + } + } + + @FunctionalInterface + public interface AdventureEntitySoundReceiver { + void play( + @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, net.kyori.adventure.sound.Sound.Emitter emitter + ); + } + +} diff --git a/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java b/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java new file mode 100644 index 00000000000..df551c268f5 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java @@ -0,0 +1,21 @@ +package ch.njol.skript.bukkitutil; + +import ch.njol.skript.Skript; +import org.bukkit.Registry; + +/** + * Utility class with methods pertaining to Bukkit API + */ +public class BukkitUtils { + + /** + * Check if a registry exists + * + * @param registry Registry to check for (Fully qualified name of registry) + * @return True if registry exists else false + */ + public static boolean registryExists(String registry) { + return Skript.classExists("org.bukkit.Registry") && Skript.fieldExists(Registry.class, registry); + } + +} diff --git a/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java b/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java index fcebff78917..7247bba5d37 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java @@ -18,26 +18,165 @@ */ package ch.njol.skript.bukkitutil; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.localization.Language; +import ch.njol.util.StringUtils; +import ch.njol.yggdrasil.Fields; import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.enchantments.Enchantment; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import ch.njol.skript.Skript; +import java.io.StreamCorruptedException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; /** * Maps enchantments to their keys. */ public class EnchantmentUtils { + private static final Map NAMES = new HashMap<>(); + private static final Map PATTERNS = new HashMap<>(); + private static final boolean HAS_REGISTRY = BukkitUtils.registryExists("ENCHANTMENT"); + + static { + if (!HAS_REGISTRY) { + Language.addListener(() -> { + NAMES.clear(); + PATTERNS.clear(); + for (Enchantment enchantment : Enchantment.values()) { + NamespacedKey key = enchantment.getKey(); + final String[] names = Language.getList("enchantments." + key.getKey()); + + if (!names[0].startsWith("enchantments.")) { + NAMES.put(enchantment, names[0]); + // Add lang file names + for (String name : names) + PATTERNS.put(name.toLowerCase(Locale.ENGLISH), enchantment); + } + // If Minecraft provided, add key without namespace and underscores (ex: "fire aspect") + if (key.getNamespace().equalsIgnoreCase(NamespacedKey.MINECRAFT)) + PATTERNS.put(key.getKey().replace("_", " "), enchantment); + // Add full namespaced key as pattern (ex: "minecraft:fire_aspect", "custom:floopy_floopy") + PATTERNS.put(key.toString(), enchantment); + } + }); + } + } + public static String getKey(Enchantment enchantment) { - return enchantment.getKey().getKey(); + return enchantment.getKey().toString(); } @Nullable public static Enchantment getByKey(String key) { - return Enchantment.getByKey(NamespacedKey.minecraft(key)); + if (!key.contains(":")) { + // Old method for old variables + return Enchantment.getByKey(NamespacedKey.minecraft(key)); + } else { + NamespacedKey namespacedKey = NamespacedKey.fromString(key); + if (namespacedKey == null) + return null; + + if (HAS_REGISTRY) { + return Registry.ENCHANTMENT.get(namespacedKey); + } else { + return Enchantment.getByKey(namespacedKey); + } + } + } + + @Nullable + public static Enchantment parseEnchantment(String s) { + return PATTERNS.get(s); + } + + @SuppressWarnings("null") + public static Collection getNames() { + return NAMES.values(); + } + + @SuppressWarnings("null") + public static String toString(final Enchantment enchantment) { + // If we have a name in the lang file, return that first + if (NAMES.containsKey(enchantment)) + return NAMES.get(enchantment); + + // If no name is available, return the namespaced key + return enchantment.getKey().toString(); + } + + // REMIND flags? + @SuppressWarnings("null") + public static String toString(final Enchantment enchantment, final int flags) { + return toString(enchantment); + } + + public static ClassInfo createClassInfo() { + return new ClassInfo<>(Enchantment.class, "enchantment") + .parser(new Parser<>() { + @Override + @Nullable + public Enchantment parse(final String s, final ParseContext context) { + return EnchantmentUtils.parseEnchantment(s); + } + + @Override + public String toString(final Enchantment e, final int flags) { + return EnchantmentUtils.toString(e, flags); + } + + @Override + public String toVariableNameString(final Enchantment e) { + return "" + EnchantmentUtils.getKey(e); + } + }).serializer(new Serializer<>() { + @Override + public Fields serialize(final Enchantment ench) { + final Fields f = new Fields(); + f.putObject("key", EnchantmentUtils.getKey(ench)); + return f; + } + + @Override + public boolean canBeInstantiated() { + return false; + } + + @Override + public void deserialize(final Enchantment o, final Fields f) { + assert false; + } + + @Override + protected Enchantment deserialize(final Fields fields) throws StreamCorruptedException { + final String key = fields.getObject("key", String.class); + assert key != null; // If a key happens to be null, something went really wrong... + final Enchantment e = EnchantmentUtils.getByKey(key); + if (e == null) + throw new StreamCorruptedException("Invalid enchantment " + key); + return e; + } + + @Override + @Nullable + public Enchantment deserialize(String s) { + return Enchantment.getByName(s); + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + }) + .usage(StringUtils.join(EnchantmentUtils.getNames(), ", ")) + .supplier(Enchantment.values()); } } diff --git a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java index 20b5120a0f1..be8675ccabb 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java @@ -26,13 +26,13 @@ import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.entity.Damageable; +import org.bukkit.entity.LivingEntity; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; public class HealthUtils { @@ -112,8 +112,11 @@ public static double getFinalDamage(EntityDamageEvent e) { return e.getFinalDamage() / 2; } - public static void setDamage(EntityDamageEvent e, double damage) { - e.setDamage(damage * 2); + public static void setDamage(EntityDamageEvent event, double damage) { + event.setDamage(damage * 2); + // Set last damage manually as Bukkit doesn't appear to do that + if (event.getEntity() instanceof LivingEntity) + ((LivingEntity) event.getEntity()).setLastDamage(damage * 2); } @Nullable diff --git a/src/main/java/ch/njol/skript/bukkitutil/InventoryUtils.java b/src/main/java/ch/njol/skript/bukkitutil/InventoryUtils.java new file mode 100644 index 00000000000..e04228edc3a --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/InventoryUtils.java @@ -0,0 +1,111 @@ +package ch.njol.skript.bukkitutil; + +import ch.njol.skript.Skript; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * Utilities for inventories. + * In newer versions (1.21+), InventoryView is an interface instead of an abstract class + * Directing calling InventoryView#getTitle on 1.20.6 and below results in an IncompatibleClassChangeError + * as an interface, not an abstract class, is expected. + */ +public class InventoryUtils { + + private static final @Nullable MethodHandle GET_TITLE; + private static final @Nullable MethodHandle GET_INVENTORY; + private static final @Nullable MethodHandle CONVERT_SLOT; + private static final @Nullable MethodHandle GET_TOP_INVENTORY; + private static final @Nullable MethodHandle GET_BOTTOM_INVENTORY; + + static { + MethodHandle getTitle = null; + MethodHandle getInventory = null; + MethodHandle convertSlot = null; + MethodHandle getTopInventory = null; + MethodHandle getBottomInventory = null; + if (!InventoryView.class.isInterface()) { // initialize legacy support as it's likely an abstract class + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + getTitle = lookup.findVirtual(InventoryView.class, "getTitle", MethodType.methodType(String.class)); + getInventory = lookup.findVirtual(InventoryView.class, "getInventory", MethodType.methodType(Inventory.class, int.class)); + convertSlot = lookup.findVirtual(InventoryView.class, "convertSlot", MethodType.methodType(int.class, int.class)); + getTopInventory = lookup.findVirtual(InventoryView.class, "getTopInventory", MethodType.methodType(Inventory.class)); + getBottomInventory = lookup.findVirtual(InventoryView.class, "getBottomInventory", MethodType.methodType(Inventory.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + Skript.exception(e, "Failed to load old inventory view support."); + } + } + GET_TITLE = getTitle; + GET_INVENTORY = getInventory; + CONVERT_SLOT = convertSlot; + GET_TOP_INVENTORY = getTopInventory; + GET_BOTTOM_INVENTORY = getBottomInventory; + } + + /** + * @see InventoryView#getTitle() + */ + public static @Nullable String getTitle(InventoryView inventoryView) { + if (GET_TITLE == null) + return inventoryView.getTitle(); + try { + return (String) GET_TITLE.invoke(inventoryView); + } catch (Throwable ignored) { } + return null; + } + + /** + * @see InventoryView#getInventory(int) + */ + public static @Nullable Inventory getInventory(InventoryView inventoryView, int rawSlot) { + if (GET_INVENTORY == null) + return inventoryView.getInventory(rawSlot); + try { + return (Inventory) GET_INVENTORY.invoke(inventoryView, rawSlot); + } catch (Throwable ignored) { } + return null; + } + + /** + * @see InventoryView#convertSlot(int) + */ + public static @Nullable Integer convertSlot(InventoryView inventoryView, int rawSlot) { + if (CONVERT_SLOT == null) + return inventoryView.convertSlot(rawSlot); + try { + return (Integer) CONVERT_SLOT.invoke(inventoryView, rawSlot); + } catch (Throwable ignored) { } + return null; + } + + /** + * @see InventoryView#getTopInventory() + */ + public static @Nullable Inventory getTopInventory(InventoryView inventoryView) { + if (GET_TOP_INVENTORY == null) + return inventoryView.getTopInventory(); + try { + return (Inventory) GET_TOP_INVENTORY.invoke(inventoryView); + } catch (Throwable ignored) { } + return null; + } + + /** + * @see InventoryView#getBottomInventory() + */ + public static @Nullable Inventory getBottomInventory(InventoryView inventoryView) { + if (GET_BOTTOM_INVENTORY == null) + return inventoryView.getBottomInventory(); + try { + return (Inventory) GET_BOTTOM_INVENTORY.invoke(inventoryView); + } catch (Throwable ignored) { } + return null; + } + +} diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 31bf99c2d56..b216f645127 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -20,8 +20,15 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.util.slot.Slot; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Fence; +import org.bukkit.block.data.type.Gate; +import org.bukkit.block.data.type.Wall; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; @@ -35,6 +42,8 @@ public class ItemUtils { public static final boolean HAS_MAX_DAMAGE = Skript.methodExists(Damageable.class, "getMaxDamage"); + // Introduced in Paper 1.21 + public static final boolean HAS_RESET = Skript.methodExists(Damageable.class, "resetDamage"); /** * Gets damage/durability of an item, or 0 if it does not have damage. @@ -42,9 +51,17 @@ public class ItemUtils { * @return Damage. */ public static int getDamage(ItemStack itemStack) { - ItemMeta meta = itemStack.getItemMeta(); - if (meta instanceof Damageable) - return ((Damageable) meta).getDamage(); + return getDamage(itemStack.getItemMeta()); + } + + /** + * Gets damage/durability of an itemmeta, or 0 if it does not have damage. + * @param itemMeta ItemMeta. + * @return Damage. + */ + public static int getDamage(ItemMeta itemMeta) { + if (itemMeta instanceof Damageable) + return ((Damageable) itemMeta).getDamage(); return 0; // Non damageable item } @@ -60,6 +77,25 @@ public static int getMaxDamage(ItemStack itemStack) { return itemStack.getType().getMaxDurability(); } + /** + * Set the max damage/durability of an item + * + * @param itemStack ItemStack to set max damage + * @param maxDamage Amount of new max damage + */ + public static void setMaxDamage(ItemStack itemStack, int maxDamage) { + ItemMeta meta = itemStack.getItemMeta(); + if (HAS_MAX_DAMAGE && meta instanceof Damageable) { + Damageable damageable = (Damageable) meta; + if (HAS_RESET && maxDamage < 1) { + damageable.resetDamage(); + } else { + damageable.setMaxDamage(Math.max(1, maxDamage)); + } + itemStack.setItemMeta(damageable); + } + } + /** * Sets damage/durability of an item if possible. * @param itemStack Item to modify. @@ -69,7 +105,7 @@ public static int getMaxDamage(ItemStack itemStack) { public static void setDamage(ItemStack itemStack, int damage) { ItemMeta meta = itemStack.getItemMeta(); if (meta instanceof Damageable) { - ((Damageable) meta).setDamage(damage); + ((Damageable) meta).setDamage(Math.max(0, damage)); itemStack.setItemMeta(meta); } } @@ -139,19 +175,44 @@ public static Material asItem(Material type) { // Assume (naively) that all types are valid items return type; } + + /** + * Convert an ItemType/Slot to ItemStack + * Will also accept an ItemStack that will return itself + * + * @param object Object to convert + * @return ItemStack from slot/itemtype + */ + @Nullable + public static ItemStack asItemStack(Object object) { + if (object instanceof ItemType) + return ((ItemType) object).getRandom(); + else if (object instanceof Slot) + return ((Slot) object).getItem(); + else if (object instanceof ItemStack) + return ((ItemStack) object); + return null; + } /** * Tests whether two item stacks are of the same type, i.e. it ignores the amounts. * - * @param is1 - * @param is2 + * @param itemStack1 + * @param itemStack2 * @return Whether the item stacks are of the same type */ - public static boolean itemStacksEqual(final @Nullable ItemStack is1, final @Nullable ItemStack is2) { - if (is1 == null || is2 == null) - return is1 == is2; - return is1.getType() == is2.getType() && ItemUtils.getDamage(is1) == ItemUtils.getDamage(is2) - && is1.getItemMeta().equals(is2.getItemMeta()); + public static boolean itemStacksEqual(@Nullable ItemStack itemStack1, @Nullable ItemStack itemStack2) { + if (itemStack1 == null || itemStack2 == null) + return itemStack1 == itemStack2; + if (itemStack1.getType() != itemStack2.getType()) + return false; + + ItemMeta itemMeta1 = itemStack1.getItemMeta(); + ItemMeta itemMeta2 = itemStack2.getItemMeta(); + if (itemMeta1 == null || itemMeta2 == null) + return itemMeta1 == itemMeta2; + + return itemStack1.getItemMeta().equals(itemStack2.getItemMeta()); } // Only 1.15 and versions after have Material#isAir method @@ -219,5 +280,56 @@ public static boolean isAir(Material type) { public static Material getTreeSapling(TreeType treeType) { return TREE_TO_SAPLING_MAP.get(treeType); } - + + + private static final boolean HAS_FENCE_TAGS = !Skript.isRunningMinecraft(1, 14); + + /** + * Whether the block is a fence or a wall. + * @param block the block to check. + * @return whether the block is a fence/wall. + */ + public static boolean isFence(Block block) { + // TODO: 1.13 only, so remove in 2.10 + if (!HAS_FENCE_TAGS) { + BlockData data = block.getBlockData(); + return data instanceof Fence + || data instanceof Wall + || data instanceof Gate; + } + + Material type = block.getType(); + return Tag.FENCES.isTagged(type) + || Tag.FENCE_GATES.isTagged(type) + || Tag.WALLS.isTagged(type); + } + + /** + * @param material The material to check + * @return whether the material is a full glass block + */ + public static boolean isGlass(Material material) { + switch (material) { + case GLASS: + case RED_STAINED_GLASS: + case ORANGE_STAINED_GLASS: + case YELLOW_STAINED_GLASS: + case LIGHT_BLUE_STAINED_GLASS: + case BLUE_STAINED_GLASS: + case CYAN_STAINED_GLASS: + case LIME_STAINED_GLASS: + case GREEN_STAINED_GLASS: + case MAGENTA_STAINED_GLASS: + case PURPLE_STAINED_GLASS: + case PINK_STAINED_GLASS: + case WHITE_STAINED_GLASS: + case LIGHT_GRAY_STAINED_GLASS: + case GRAY_STAINED_GLASS: + case BLACK_STAINED_GLASS: + case BROWN_STAINED_GLASS: + return true; + default: + return false; + } + } } diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java index eef0866a01b..87bc45a6ec8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java @@ -65,6 +65,9 @@ default BlockValues getBlockValues(Block block) { return getBlockValues(block.getBlockData()); } + @Nullable + BlockValues getBlockValues(Material material); + @Nullable BlockValues getBlockValues(BlockData blockData); diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java index 2a0ecd36da8..a1413a8ad35 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -22,6 +22,7 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.aliases.MatchQuality; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.variables.Variables; import ch.njol.yggdrasil.Fields; import org.bukkit.Bukkit; @@ -31,10 +32,12 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; +import org.bukkit.block.BlockSupport; import org.bukkit.block.data.Bisected; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Directional; import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.Snow; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -141,15 +144,6 @@ public void deserialize(@NonNull Fields fields) throws StreamCorruptedException } private static class NewBlockSetter implements BlockSetter { - - private ItemType floorTorch; - private ItemType wallTorch; - - private ItemType specialTorchSides; - private ItemType specialTorchFloors; - - private boolean typesLoaded = false; - private static final BlockFace[] CARDINAL_FACES = new BlockFace[] {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}; @@ -158,14 +152,11 @@ public NewBlockSetter() {} @Override public void setBlock(Block block, Material type, @Nullable BlockValues values, int flags) { - if (!typesLoaded) - loadTypes(); - - boolean rotate = (flags | ROTATE) != 0; - boolean rotateForce = (flags | ROTATE_FORCE) != 0; - boolean rotateFixType = (flags | ROTATE_FIX_TYPE) != 0; - boolean multipart = (flags | MULTIPART) != 0; - boolean applyPhysics = (flags | APPLY_PHYSICS) != 0; + boolean rotate = (flags & ROTATE) != 0; + boolean rotateForce = (flags & ROTATE_FORCE) != 0; + boolean rotateFixType = (flags & ROTATE_FIX_TYPE) != 0; + boolean multipart = (flags & MULTIPART) != 0; + boolean applyPhysics = (flags & APPLY_PHYSICS) != 0; NewBlockValues ourValues = null; if (values != null) ourValues = (NewBlockValues) values; @@ -178,31 +169,23 @@ public void setBlock(Block block, Material type, @Nullable BlockValues values, i */ boolean placed = false; if (rotate) { - if (floorTorch.isOfType(type) || (rotateFixType && wallTorch.isOfType(type))) { + if (type == Material.TORCH || (rotateFixType && type == Material.WALL_TORCH)) { // If floor torch cannot be placed, try a wall torch Block under = block.getRelative(0, -1, 0); - boolean canPlace = true; - if (!under.getType().isOccluding()) { // Usually cannot be placed, but there are exceptions - // TODO check for stairs and slabs, currently complicated since there is no 'any' alias - if (specialTorchFloors.isOfType(under)) { - canPlace = true; - } else { - canPlace = false; - } - } + boolean canPlace = canSupportFloorTorch(under); // Can't really place a floor torch, try wall one instead if (!canPlace) { BlockFace face = findWallTorchSide(block); if (face != null) { // Found better torch spot - block.setType(wallTorch.getMaterial()); + block.setType(Material.WALL_TORCH); Directional data = (Directional) block.getBlockData(); data.setFacing(face); block.setBlockData(data, applyPhysics); placed = true; } } - } else if (wallTorch.isOfType(type)) { + } else if (type == Material.WALL_TORCH) { Directional data; if (ourValues != null) data = (Directional) ourValues.data; @@ -210,7 +193,7 @@ public void setBlock(Block block, Material type, @Nullable BlockValues values, i data = (Directional) Bukkit.createBlockData(type); Block relative = block.getRelative(data.getFacing()); - if ((!relative.getType().isOccluding() && !specialTorchSides.isOfType(relative)) || rotateForce) { + if ((!canSupportWallTorch(relative, data.getFacing().getOppositeFace())) || rotateForce) { // Attempt to figure out a better rotation BlockFace face = findWallTorchSide(block); if (face != null) { // Found better torch spot @@ -294,22 +277,49 @@ public void setBlock(Block block, Material type, @Nullable BlockValues values, i block.setBlockData(ourValues.data, applyPhysics); } } - - private void loadTypes() { - floorTorch = Aliases.javaItemType("floor torch"); - wallTorch = Aliases.javaItemType("wall torch"); - - specialTorchSides = Aliases.javaItemType("special torch sides"); - specialTorchFloors = Aliases.javaItemType("special torch floors"); - - typesLoaded = true; + + // 1.19+ + // TODO: remove in 2.10 + private static final boolean HAS_BLOCK_SUPPORT = Skript.classExists("org.bukkit.block.BlockSupport"); + + /** + * Returns whether this block can support a floor torch. + * @param block The block the torch will be placed on + * @return whether the block can support the torch. + */ + private static boolean canSupportFloorTorch(Block block) { + if (HAS_BLOCK_SUPPORT) + return block.getBlockData().isFaceSturdy(BlockFace.UP, BlockSupport.CENTER); + + Material material = block.getType(); + return (material.isOccluding() + || canSupportWallTorch(block, null) + || ItemUtils.isFence(block) + || ItemUtils.isGlass(material) + || material == Material.HOPPER + || (material == Material.SNOW && (((Snow) block.getBlockData()).getLayers() == 8)) + ); + } + + /** + * Returns whether this block can support a wall torch. In 1.19+, a face can be specified. + * @param block The block the torch will be placed on + * @param face The face the torch will be placed on (only considered in 1.19+) + * @return whether the block face can support the torch. + */ + private static boolean canSupportWallTorch(Block block, @Nullable BlockFace face) { + if (HAS_BLOCK_SUPPORT && face != null) + return block.getBlockData().isFaceSturdy(face, BlockSupport.FULL); + + Material material = block.getType(); + return material.isOccluding() || material == Material.SOUL_SAND || material == Material.SPAWNER; } @Nullable private BlockFace findWallTorchSide(Block block) { for (BlockFace face : CARDINAL_FACES) { Block relative = block.getRelative(face); - if (relative.getType().isOccluding() || specialTorchSides.isOfType(relative)) + if (relative.getType().isOccluding() || canSupportWallTorch(relative, face.getOppositeFace())) return face.getOppositeFace(); // Torch can be rotated towards from this face } @@ -336,6 +346,13 @@ public BlockValues getBlockValues(BlockState blockState) { return getBlockValues(blockState.getBlockData()); } + @Override + public @Nullable BlockValues getBlockValues(Material material) { + if (material.isBlock()) + return new NewBlockValues(material, Bukkit.createBlockData(material), true); + return null; + } + @Nullable @Override public BlockValues getBlockValues(BlockData blockData) { diff --git a/src/main/java/ch/njol/skript/classes/Changer.java b/src/main/java/ch/njol/skript/classes/Changer.java index d3b413531fe..a73f40b439a 100644 --- a/src/main/java/ch/njol/skript/classes/Changer.java +++ b/src/main/java/ch/njol/skript/classes/Changer.java @@ -29,14 +29,13 @@ * isn't overridden. *

* Some useful Changers can be found in {@link DefaultChangers} - * - * @author Peter Güttinger + * * @see DefaultChangers * @see Expression */ public interface Changer { - public static enum ChangeMode { + enum ChangeMode { ADD, SET, REMOVE, REMOVE_ALL, DELETE, RESET; } @@ -45,45 +44,44 @@ public static enum ChangeMode { *

* Unlike {@link Expression#acceptChange(ChangeMode)} this method must not print errors. * - * @param mode + * @param mode The {@link ChangeMode} to test. * @return An array of types that {@link #change(Object[], Object[], ChangeMode)} accepts as its delta parameter (which can be arrays to denote that multiple of * that type are accepted), or null if the given mode is not supported. For {@link ChangeMode#DELETE} and {@link ChangeMode#RESET} this can return any non-null array to * mark them as supported. */ - @Nullable - public abstract Class[] acceptChange(ChangeMode mode); + Class @Nullable [] acceptChange(ChangeMode mode); /** * @param what The objects to change * @param delta An array with one or more instances of one or more of the the classes returned by {@link #acceptChange(ChangeMode)} for the given change mode (null for * {@link ChangeMode#DELETE} and {@link ChangeMode#RESET}). This can be a Object[], thus casting is not allowed. - * @param mode + * @param mode The {@link ChangeMode} to test. * @throws UnsupportedOperationException (optional) if this method was called on an unsupported ChangeMode. */ - public abstract void change(T[] what, @Nullable Object[] delta, ChangeMode mode); + void change(T[] what, Object @Nullable [] delta, ChangeMode mode); - public static abstract class ChangerUtils { - - @SuppressWarnings("unchecked") - public static void change(final Changer changer, final Object[] what, final @Nullable Object[] delta, final ChangeMode mode) { + abstract class ChangerUtils { + + public static void change(Changer changer, Object[] what, Object @Nullable [] delta, ChangeMode mode) { + //noinspection unchecked changer.change((T[]) what, delta, mode); } /** * Tests whether an expression accepts changes of a certain type. If multiple types are given it test for whether any of the types is accepted. * - * @param e The expression to test + * @param expression The expression to test * @param mode The ChangeMode to use in the test * @param types The types to test for - * @return Whether e.{@link Expression#change(Event, Object[], ChangeMode) change}(event, type[], mode) can be used or not. + * @return Whether expression.{@link Expression#change(Event, Object[], ChangeMode) change}(event, type[], mode) can be used or not. */ - public static boolean acceptsChange(final Expression e, final ChangeMode mode, final Class... types) { - final Class[] cs = e.acceptChange(mode); - if (cs == null) + public static boolean acceptsChange(final Expression expression, final ChangeMode mode, final Class... types) { + final Class[] validTypes = expression.acceptChange(mode); + if (validTypes == null) return false; for (final Class type : types) { - for (final Class c : cs) { - if (c.isArray() ? c.getComponentType().isAssignableFrom(type) : c.isAssignableFrom(type)) + for (final Class validType : validTypes) { + if (validType.isArray() ? validType.getComponentType().isAssignableFrom(type) : validType.isAssignableFrom(type)) return true; } } 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 ba1a419aad0..ba0a66a1f41 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -29,6 +29,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import ch.njol.skript.bukkitutil.BukkitUtils; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Difficulty; @@ -99,7 +100,6 @@ import ch.njol.skript.localization.Language; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.PotionEffectUtils; import ch.njol.skript.util.StringMode; import ch.njol.util.StringUtils; @@ -355,7 +355,7 @@ public boolean mustSyncDeserialization() { protected boolean canBeInstantiated() { return false; } - })); + }).cloner(BlockData::clone)); Classes.registerClass(new ClassInfo<>(Location.class, "location") .user("locations?") @@ -932,6 +932,7 @@ public String toVariableNameString(InventoryHolder holder) { .since("1.0") .after("number") .supplier(() -> Arrays.stream(Material.values()) + .filter(Material::isItem) .map(ItemStack::new) .iterator()) .parser(new Parser() { @@ -948,7 +949,10 @@ public ItemStack parse(final String s, final ParseContext context) { } final ItemStack i = t.getRandom(); - assert i != null; + if (i == null) { + Skript.error("'" + s + "' cannot represent an item"); + return null; + } return i; } @@ -980,7 +984,7 @@ public String toVariableNameString(final ItemStack i) { .changer(DefaultChangers.itemChanger)); ClassInfo biomeClassInfo; - if (Skript.classExists("org.bukkit.Registry") && Skript.fieldExists(Registry.class, "BIOME")) { + if (BukkitUtils.registryExists("BIOME")) { biomeClassInfo = new RegistryClassInfo<>(Biome.class, Registry.BIOME, "biome", "biomes"); } else { biomeClassInfo = new EnumClassInfo<>(Biome.class, "biome", "biomes"); @@ -988,7 +992,8 @@ public String toVariableNameString(final ItemStack i) { Classes.registerClass(biomeClassInfo .user("biomes?") .name("Biome") - .description("All possible biomes Minecraft uses to generate a world.") + .description("All possible biomes Minecraft uses to generate a world.", + "NOTE: Minecraft namespaces are supported, ex: 'minecraft:basalt_deltas'.") .examples("biome at the player is desert") .since("1.4.4") .after("damagecause")); @@ -1075,12 +1080,12 @@ protected boolean canBeInstantiated() { public PotionEffectType parse(final String s, final ParseContext context) { return PotionEffectUtils.parseType(s); } - + @Override public String toString(final PotionEffectType p, final int flags) { return PotionEffectUtils.toString(p, flags); } - + @Override public String toVariableNameString(final PotionEffectType p) { return "" + p.getName(); @@ -1220,74 +1225,23 @@ public boolean mustSyncDeserialization() { return true; } })); - - Classes.registerClass(new ClassInfo<>(Enchantment.class, "enchantment") + + ClassInfo enchantmentClassInfo; + if (BukkitUtils.registryExists("ENCHANTMENT")) { + enchantmentClassInfo = new RegistryClassInfo<>(Enchantment.class, Registry.ENCHANTMENT, "enchantment", "enchantments"); + } else { + enchantmentClassInfo = EnchantmentUtils.createClassInfo(); + } + Classes.registerClass(enchantmentClassInfo .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.") - .usage(StringUtils.join(EnchantmentType.getNames(), ", ")) + "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") - .supplier(Enchantment.values()) - .parser(new Parser() { - @Override - @Nullable - public Enchantment parse(final String s, final ParseContext context) { - return EnchantmentType.parseEnchantment(s); - } - - @Override - public String toString(final Enchantment e, final int flags) { - return EnchantmentType.toString(e, flags); - } - - @Override - public String toVariableNameString(final Enchantment e) { - return "" + EnchantmentUtils.getKey(e); - } - }) - .serializer(new Serializer() { - @Override - public Fields serialize(final Enchantment ench) { - final Fields f = new Fields(); - f.putObject("key", EnchantmentUtils.getKey(ench)); - return f; - } - - @Override - public boolean canBeInstantiated() { - return false; - } - - @Override - public void deserialize(final Enchantment o, final Fields f) { - assert false; - } - - @Override - protected Enchantment deserialize(final Fields fields) throws StreamCorruptedException { - final String key = fields.getObject("key", String.class); - assert key != null; // If a key happens to be null, something went really wrong... - final Enchantment e = EnchantmentUtils.getByKey(key); - if (e == null) - throw new StreamCorruptedException("Invalid enchantment " + key); - return e; - } - - // return "" + e.getId(); - @Override - @Nullable - public Enchantment deserialize(String s) { - return Enchantment.getByName(s); - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); + .before("enchantmenttype")); Material[] allMaterials = Material.values(); Classes.registerClass(new ClassInfo<>(Material.class, "material") @@ -1446,17 +1400,26 @@ public String toVariableNameString(FireworkEffect effect) { .since("2.4") .requiredPlugins("Minecraft 1.14 or newer")); } + Classes.registerClass(new EnumClassInfo<>(RegainReason.class, "healreason", "heal reasons") - .user("(regen|heal) (reason|cause)") - .name("Heal Reason") - .description("The heal reason in a heal event.") - .examples("") - .since("2.5")); + .user("(regen|heal) (reason|cause)") + .name("Heal Reason") + .description("The health regain reason in a heal event.") + .since("2.5")); + if (Skript.classExists("org.bukkit.entity.Cat$Type")) { - Classes.registerClass(new EnumClassInfo<>(Cat.Type.class, "cattype", "cat types") + ClassInfo catTypeClassInfo; + if (BukkitUtils.registryExists("CAT_VARIANT")) { + catTypeClassInfo = new RegistryClassInfo<>(Cat.Type.class, Registry.CAT_VARIANT, "cattype", "cat types"); + } else { + //noinspection unchecked, rawtypes - it is an enum on other versions + catTypeClassInfo = new EnumClassInfo<>((Class) Cat.Type.class, "cattype", "cat types"); + } + Classes.registerClass(catTypeClassInfo .user("cat ?(type|race)s?") .name("Cat Type") - .description("Represents the race/type of a cat entity.") + .description("Represents the race/type of a cat entity.", + "NOTE: Minecraft namespaces are supported, ex: 'minecraft:british_shorthair'.") .since("2.4") .requiredPlugins("Minecraft 1.14 or newer") .documentationId("CatType")); @@ -1505,20 +1468,27 @@ public boolean canParse(ParseContext context) { @Override public String toString(EnchantmentOffer eo, int flags) { - return EnchantmentType.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); + return EnchantmentUtils.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); } @Override public String toVariableNameString(EnchantmentOffer eo) { - return "offer:" + EnchantmentType.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); + return "offer:" + EnchantmentUtils.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); } })); - Classes.registerClass(new EnumClassInfo<>(Attribute.class, "attributetype", "attribute types") + ClassInfo attributeClassInfo; + if (BukkitUtils.registryExists("ATTRIBUTE")) { + attributeClassInfo = new RegistryClassInfo<>(Attribute.class, Registry.ATTRIBUTE, "attributetype", "attribute types"); + } else { + attributeClassInfo = new EnumClassInfo<>(Attribute.class, "attributetype", "attribute types"); + } + Classes.registerClass(attributeClassInfo .user("attribute ?types?") .name("Attribute Type") .description("Represents the type of an attribute. Note that this type does not contain any numerical values." - + "See attribute types for more info.") + + "See attribute types for more info.", + "NOTE: Minecraft namespaces are supported, ex: 'minecraft:generic.attack_damage'.") .since("2.5")); Classes.registerClass(new EnumClassInfo<>(Environment.class, "environment", "environments") 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 76e098cb602..e430dd472f6 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -26,6 +26,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.InventoryUtils; import ch.njol.skript.command.CommandEvent; import ch.njol.skript.events.bukkit.ScriptEvent; import ch.njol.skript.events.bukkit.SkriptStartEvent; @@ -101,8 +102,11 @@ import org.bukkit.event.entity.EntityDropItemEvent; import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; +import org.bukkit.event.entity.EntityTeleportEvent; import org.bukkit.event.entity.EntityTransformEvent; import org.bukkit.event.entity.EntityTransformEvent.TransformReason; import org.bukkit.event.entity.FireworkExplodeEvent; @@ -390,12 +394,11 @@ public Block get(final BlockBreakEvent e) { return new DelayedChangeBlock(e.getBlock()); } }, 0); - ItemType stationaryWater = Aliases.javaItemType("stationary water"); EventValues.registerEventValue(BlockBreakEvent.class, Block.class, new Getter() { @Override public Block get(final BlockBreakEvent e) { final BlockState s = e.getBlock().getState(); - s.setType(s.getType() == Material.ICE ? stationaryWater.getMaterial() : Material.AIR); + s.setType(s.getType() == Material.ICE ? Material.WATER : Material.AIR); s.setRawData((byte) 0); return new BlockStateBlock(s, true); } @@ -623,6 +626,20 @@ public Entity get(final EntityTameEvent e) { } }, 0); + // EntityTeleportEvent + EventValues.registerEventValue(EntityTeleportEvent.class, Location.class, new Getter() { + @Override + public @Nullable Location get(EntityTeleportEvent event) { + return event.getFrom(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(EntityTeleportEvent.class, Location.class, new Getter() { + @Override + public @Nullable Location get(EntityTeleportEvent event) { + return event.getTo(); + } + }, EventValues.TIME_NOW); + // EntityChangeBlockEvent EventValues.registerEventValue(EntityChangeBlockEvent.class, Block.class, new Getter() { @Override @@ -758,12 +775,11 @@ public Block get(final PlayerBucketEmptyEvent e) { return e.getBlockClicked().getRelative(e.getBlockFace()); } }, -1); - ItemType stationaryLava = Aliases.javaItemType("stationary lava"); EventValues.registerEventValue(PlayerBucketEmptyEvent.class, Block.class, new Getter() { @Override public Block get(final PlayerBucketEmptyEvent e) { final BlockState s = e.getBlockClicked().getRelative(e.getBlockFace()).getState(); - s.setType(e.getBucket() == Material.WATER_BUCKET ? stationaryWater.getMaterial() : stationaryLava.getMaterial()); + s.setType(e.getBucket() == Material.WATER_BUCKET ? Material.WATER : Material.LAVA); s.setRawData((byte) 0); return new BlockStateBlock(s, true); } @@ -1237,15 +1253,15 @@ public Slot[] get(InventoryDragEvent event) { List slots = new ArrayList<>(event.getRawSlots().size()); InventoryView view = event.getView(); for (Integer rawSlot : event.getRawSlots()) { - Inventory inventory = view.getInventory(rawSlot); - int slot = view.convertSlot(rawSlot); - if (inventory == null) + Inventory inventory = InventoryUtils.getInventory(view, rawSlot); + Integer slot = InventoryUtils.convertSlot(view, rawSlot); + if (inventory == null || slot == null) continue; // Not all indices point to inventory slots. Equipment, for example if (inventory instanceof PlayerInventory && slot >= 36) { slots.add(new ch.njol.skript.util.slot.EquipmentSlot(((PlayerInventory) view.getBottomInventory()).getHolder(), slot)); } else { - slots.add(new InventorySlot(inventory, view.convertSlot(rawSlot))); + slots.add(new InventorySlot(inventory, slot)); } } return slots.toArray(new Slot[0]); @@ -1265,7 +1281,9 @@ public Inventory[] get(InventoryDragEvent event) { Set inventories = new HashSet<>(); InventoryView view = event.getView(); for (Integer rawSlot : event.getRawSlots()) { - inventories.add(view.getInventory(rawSlot)); + Inventory inventory = InventoryUtils.getInventory(view, rawSlot); + if (inventory != null) + inventories.add(inventory); } return inventories.toArray(new Inventory[0]); } @@ -1908,5 +1926,14 @@ public ItemStack get(InventoryMoveItemEvent event) { return event.getItem(); } }, EventValues.TIME_NOW); + + // EntityRegainHealthEvent + EventValues.registerEventValue(EntityRegainHealthEvent.class, RegainReason.class, new Getter() { + @Override + @Nullable + public RegainReason get(EntityRegainHealthEvent event) { + return event.getRegainReason(); + } + }, EventValues.TIME_NOW); } } 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 e59a5678403..e5e42bf1423 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -60,6 +60,7 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; @@ -495,7 +496,6 @@ public boolean supportsOrdering() { }); // DamageCause - ItemType - ItemType lava = Aliases.javaItemType("lava"); Comparators.registerComparator(DamageCause.class, ItemType.class, new Comparator() { @Override public Relation compare(DamageCause dc, ItemType t) { @@ -503,7 +503,7 @@ public Relation compare(DamageCause dc, ItemType t) { case FIRE: return Relation.get(t.isOfType(Material.FIRE)); case LAVA: - return Relation.get(t.equals(lava)); + return Relation.get(t.getMaterial() == Material.LAVA); case MAGIC: return Relation.get(t.isOfType(Material.POTION)); case HOT_FLOOR: @@ -653,6 +653,9 @@ public boolean supportsOrdering() { return false; } }); + + // Potion Effect Type + Comparators.registerComparator(PotionEffectType.class, PotionEffectType.class, (one, two) -> Relation.get(one.equals(two))); } } 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 00f419f4901..a90de6957d3 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -176,6 +176,14 @@ public DefaultConverters() {} return ((Block) holder).getLocation(); if (holder instanceof BlockState) return BlockUtils.getLocation(((BlockState) holder).getBlock()); + if (holder instanceof DoubleChest) { + DoubleChest doubleChest = (DoubleChest) holder; + if (doubleChest.getLeftSide() != null) { + return BlockUtils.getLocation(((BlockState) doubleChest.getLeftSide()).getBlock()); + } else if (((DoubleChest) holder).getRightSide() != null) { + return BlockUtils.getLocation(((BlockState) doubleChest.getRightSide()).getBlock()); + } + } return null; }); 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 afc46cba346..10baab6fb8c 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 @@ */ package ch.njol.skript.classes.data; +import ch.njol.skript.Skript; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.function.FunctionEvent; @@ -45,7 +46,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Calendar; +import java.util.List; import java.util.UUID; public class DefaultFunctions { @@ -376,7 +379,7 @@ public World[] executeSimple(Object[][] params) { }, DefaultClasses.LOCATION, true) { @Override @Nullable - public Location[] execute(FunctionEvent e, Object[][] params) { + public Location[] execute(FunctionEvent event, Object[][] params) { for (int i : new int[] {0, 1, 2, 4, 5}) { if (params[i] == null || params[i].length == 0 || params[i][0] == null) return null; @@ -556,23 +559,54 @@ public Player[] executeSimple(Object[][] params) { .examples("set {_p} to player(\"Notch\") # will return an online player whose name is or starts with 'Notch'", "set {_p} to player(\"Notch\", true) # will return the only online player whose name is 'Notch'", "set {_p} to player(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\") # if player is offline") .since("2.8.0"); - Functions.registerFunction(new SimpleJavaFunction("offlineplayer", new Parameter[] { - new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null) - }, DefaultClasses.OFFLINE_PLAYER, true) { - @Override - public OfflinePlayer[] executeSimple(Object[][] params) { - String name = (String) params[0][0]; - UUID uuid = null; - if (name.length() > 16 || name.contains("-")) { // shortcut - try { - uuid = UUID.fromString(name); - } catch (IllegalArgumentException ignored) {} + { // offline player function + boolean hasIfCached = Skript.methodExists(Bukkit.class, "getOfflinePlayerIfCached", String.class); + + List> params = new ArrayList<>(); + params.add(new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null)); + if (hasIfCached) + params.add(new Parameter<>("allowLookups", DefaultClasses.BOOLEAN, true, new SimpleLiteral<>(true, true))); + + Functions.registerFunction(new SimpleJavaFunction("offlineplayer", params.toArray(new Parameter[0]), + DefaultClasses.OFFLINE_PLAYER, true) { + @Override + public OfflinePlayer[] executeSimple(Object[][] params) { + String name = (String) params[0][0]; + UUID uuid = null; + if (name.length() > 16 || name.contains("-")) { // shortcut + try { + uuid = UUID.fromString(name); + } catch (IllegalArgumentException ignored) { + } + } + OfflinePlayer result; + + if (uuid != null) { + result = Bukkit.getOfflinePlayer(uuid); // doesn't do lookups + } else if (hasIfCached && !((Boolean) params[1][0])) { + result = Bukkit.getOfflinePlayerIfCached(name); + if (result == null) + return new OfflinePlayer[0]; + } else { + result = Bukkit.getOfflinePlayer(name); + } + + return CollectionUtils.array(result); } - return CollectionUtils.array(uuid != null ? Bukkit.getOfflinePlayer(uuid) : Bukkit.getOfflinePlayer(name)); - } - }).description("Returns a offline player from their name or UUID. This function will still return the player if they're online.") - .examples("set {_p} to offlineplayer(\"Notch\")", "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") - .since("2.8.0"); + + }).description( + "Returns a offline player from their name or UUID. This function will still return the player if they're online. " + + "If Paper 1.16.5+ is used, the 'allowLookup' parameter can be set to false to prevent this function from doing a " + + "web lookup for players who have not joined before. Lookups can cause lag spikes of up to multiple seconds, so " + + "use offline players with caution." + ) + .examples( + "set {_p} to offlineplayer(\"Notch\")", + "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")", + "set {_p} to offlineplayer(\"Notch\", false)" + ) + .since("2.8.0, 2.9.0 (prevent lookups)"); + } // end offline player function Functions.registerFunction(new SimpleJavaFunction("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { @Override @@ -598,7 +632,7 @@ public String[] executeSimple(Object[][] params) { .examples( "concat(\"hello \", \"there\") # hello there", "concat(\"foo \", 100, \" bar\") # foo 100 bar" - ).since("INSERT VERSION"); + ).since("2.9.0"); } 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 51cb4637434..dc9bf9f00b7 100644 --- a/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java @@ -30,12 +30,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; /** * A parser based on a {@link Registry} used to parse data from a string or turn data into a string. @@ -147,8 +146,7 @@ private void refresh() { * Note that some entries may represent the same registry object. */ public String getAllNames() { - List strings = new ArrayList<>(parseMap.keySet()); - Collections.sort(strings); + List strings = parseMap.keySet().stream().filter(s -> !s.startsWith("minecraft:")).sorted().collect(Collectors.toList()); return StringUtils.join(strings, ", "); } diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java index ddec6c3225f..33f6645fe52 100644 --- a/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java +++ b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java @@ -23,6 +23,7 @@ import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; import java.io.StreamCorruptedException; @@ -40,29 +41,29 @@ public RegistrySerializer(Registry registry) { } @Override - public Fields serialize(R o) { + public @NotNull Fields serialize(R o) { Fields fields = new Fields(); - fields.putPrimitive("name", o.getKey().toString()); - return null; + fields.putObject("name", o.getKey().toString()); + return fields; } @Override - protected R deserialize(Fields fields) { - try { - String name = fields.getAndRemovePrimitive("name", String.class); - NamespacedKey namespacedKey; - if (!name.contains(":")) { - // Old variables - namespacedKey = NamespacedKey.minecraft(name); - } else { - namespacedKey = NamespacedKey.fromString(name); - } - if (namespacedKey == null) - return null; - return registry.get(namespacedKey); - } catch (StreamCorruptedException e) { - return null; + protected R deserialize(Fields fields) throws StreamCorruptedException { + String name = fields.getAndRemoveObject("name", String.class); + assert name != null; + NamespacedKey namespacedKey; + if (!name.contains(":")) { + // Old variables + namespacedKey = NamespacedKey.minecraft(name); + } else { + namespacedKey = NamespacedKey.fromString(name); } + if (namespacedKey == null) + throw new StreamCorruptedException("Invalid namespacedkey: " + name); + R object = registry.get(namespacedKey); + if (object == null) + throw new StreamCorruptedException("Invalid object from registry: " + namespacedKey); + return object; } @Override diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index cff08f617ae..398bdbdb12e 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -184,7 +184,7 @@ public void onServerCommand(ServerCommandEvent event) { }; static boolean handleEffectCommand(CommandSender sender, String command) { - if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) + if (!(Skript.testing() || sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; try { command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim(); @@ -300,7 +300,7 @@ public static void registerListeners() { Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerChat(AsyncPlayerChatEvent event) { - if (!SkriptConfig.enableEffectCommands.value() || !event.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) + if ((!SkriptConfig.enableEffectCommands.value() && !Skript.testing()) || !event.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) return; if (!event.isAsynchronous()) { if (handleEffectCommand(event.getPlayer(), event.getMessage())) diff --git a/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java index 924fbd8a710..e52d8f08992 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java @@ -35,7 +35,7 @@ "if {_items::*} aren't resistant to fire:" }) @RequiredPlugins("Spigot 1.20.5+") -@Since("INSERT VERSION") +@Since("2.9.0") public class CondIsFireResistant extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java index 3fb40549421..111d06baebc 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java @@ -54,7 +54,7 @@ "clear entity within {_entity}" }) @RequiredPlugins("Paper") -@Since("INSERT VERSION") +@Since("2.9.0") public class CondIsPathfinding extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java index 195e8240d93..f69a183adfd 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java @@ -80,13 +80,14 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye public boolean check(Event event) { return blocks.check(event, block -> items.check(event, item -> { - if (block instanceof Block) { - return ((Block) block).isPreferredTool(item.getRandom()); - } else if (block instanceof BlockData) { - return ((BlockData) block).isPreferredTool(item.getRandom()); - } else { - return false; + ItemStack stack = item.getRandom(); + if (stack != null) { + if (block instanceof Block) + return ((Block) block).isPreferredTool(stack); + if (block instanceof BlockData) + return ((BlockData) block).isPreferredTool(stack); } + return false; }), isNegated()); } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsResonating.java b/src/main/java/ch/njol/skript/conditions/CondIsResonating.java index c2614aaead1..0e255b87e16 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsResonating.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsResonating.java @@ -36,7 +36,7 @@ }) @Examples("target block is resonating") @RequiredPlugins("Spigot 1.19.4+") -@Since("INSERT VERSION") +@Since("2.9.0") public class CondIsResonating extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsRinging.java b/src/main/java/ch/njol/skript/conditions/CondIsRinging.java index c7ddca50b1a..49bda5d6672 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsRinging.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsRinging.java @@ -33,7 +33,7 @@ @Description("Checks to see if a bell is currently ringing. A bell typically rings for 50 game ticks.") @Examples("target block is ringing") @RequiredPlugins("Spigot 1.19.4+") -@Since("INSERT VERSION") +@Since("2.9.0") public class CondIsRinging extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsTamed.java b/src/main/java/ch/njol/skript/conditions/CondIsTamed.java new file mode 100644 index 00000000000..a3e342deef6 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsTamed.java @@ -0,0 +1,34 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Tameable; + +@Name("Is Tamed") +@Description("Check if a tameable entity is tamed (horse, parrot, cat, etc.).") +@Examples({ + "send true if {_horse} is tamed", + "tame {_horse} if {_horse} is untamed" +}) +@Since("INSERT VERSION") +public class CondIsTamed extends PropertyCondition { + + static { + register(CondIsTamed.class, "(tamed|domesticated)", "entities"); + } + + @Override + public boolean check(Entity entity) { + return (entity instanceof Tameable) && ((Tameable) entity).isTamed(); + } + + @Override + protected String getPropertyName() { + return "tamed"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java b/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java index b4886661517..5a0a7d4cd58 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsUnbreakable.java @@ -37,7 +37,7 @@ "if tool of {_p} is breakable:", "\tsend \"Your tool is breakable!\" to {_p}" }) -@Since("2.5.1, INSERT VERSION (breakable)") +@Since("2.5.1, 2.9.0 (breakable)") public class CondIsUnbreakable extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java index 4ce1c0ef0bd..39a23b740ab 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java @@ -39,7 +39,7 @@ "on load:", "\tif the script is using \"example feature\":", "\t\tbroadcast \"You're using an experimental feature!\""}) -@Since("INSERT VERSION") +@Since("2.9.0") public class CondIsUsingFeature extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java b/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java index e0d50f25c33..2f771590ff9 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWhitelisted.java @@ -40,7 +40,7 @@ "if the server is whitelisted:", "if the server whitelist is enforced:" }) -@Since("2.5.2, INSERT VERSION (enforce, offline players)") +@Since("2.5.2, 2.9.0 (enforce, offline players)") @RequiredPlugins("MC 1.17+ (enforce)") public class CondIsWhitelisted extends Condition { diff --git a/src/main/java/ch/njol/skript/conditions/CondTooltip.java b/src/main/java/ch/njol/skript/conditions/CondTooltip.java new file mode 100644 index 00000000000..fd1ce0515cb --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondTooltip.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Has Item Tooltips") +@Description({ + "Whether the entire or additional tooltip of an item is shown or hidden.", + "The 'entire tooltip' is what shows to the player when they hover an item (i.e. name, lore, etc.).", + "The 'additional tooltip' hides certain information from certain items (potions, maps, books, fireworks, and banners)." +}) +@Examples({ + "send true if entire tooltip of player's tool is shown", + "if additional tooltip of {_item} is hidden:" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("2.9.0") +public class CondTooltip extends Condition { + + static { + if (Skript.methodExists(ItemMeta.class, "isHideTooltip")) {// this method was added in the same version as the additional tooltip item flag + Skript.registerCondition(CondTooltip.class, + "[the] [entire|:additional] tool[ ]tip[s] of %itemtypes% (is|are) (:shown|hidden)", + "[the] [entire|:additional] tool[ ]tip[s] of %itemtypes% (isn't|is not|aren't|are not) (:shown|hidden)", + "%itemtypes%'[s] [entire|:additional] tool[ ]tip[s] (is|are) (:shown|hidden)", + "%itemtypes%'[s] [entire|:additional] tool[ ]tip[s] (isn't|is not|aren't|are not) (:shown|hidden)" + ); + } + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression items; + private boolean entire; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression) exprs[0]; + entire = !parseResult.hasTag("additional"); + setNegated(parseResult.hasTag("shown") ^ (matchedPattern == 1 || matchedPattern == 3)); + return true; + } + + @Override + public boolean check(Event event) { + if (entire) + return items.check(event, item -> item.getItemMeta().isHideTooltip(), isNegated()); + return items.check(event, item -> item.getItemMeta().hasItemFlag(ItemFlag.HIDE_ADDITIONAL_TOOLTIP), isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the " + (entire ? "entire" : "additional") + " tooltip of " + items.toString(event, debug) + " is " + (isNegated() ? "hidden" : "shown"); + } + +} diff --git a/src/main/java/ch/njol/skript/config/Node.java b/src/main/java/ch/njol/skript/config/Node.java index f59c7210f5c..cff13bda618 100644 --- a/src/main/java/ch/njol/skript/config/Node.java +++ b/src/main/java/ch/njol/skript/config/Node.java @@ -23,6 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import ch.njol.skript.SkriptConfig; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -114,17 +115,27 @@ public void move(final SectionNode newParent) { p.remove(this); newParent.add(this); } - - @SuppressWarnings("null") - private final static Pattern linePattern = Pattern.compile("^((?:[^#]|##)*)(\\s*#(?!#).*)$"); - + /** * Splits a line into value and comment. *

* Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## in the value are replaced by a single #. The comment is returned with a * leading #, except if there is no comment in which case it will be the empty string. + * + * @param line the line to split + * @return A pair (value, comment). + */ + public static NonNullPair splitLine(String line) { + return splitLine(line, new AtomicBoolean(false)); + } + + /** + * Splits a line into value and comment. + *

+ * Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## not in quoted strings in the value are replaced by a single #. The comment is returned with a + * leading #, except if there is no comment in which case it will be the empty string. * - * @param line + * @param line the line to split * @param inBlockComment Whether we are currently inside a block comment * @return A pair (value, comment). */ @@ -138,34 +149,99 @@ public static NonNullPair splitLine(String line, AtomicBoolean i } else if (inBlockComment.get()) { // we're inside a comment, all text is a comment return new NonNullPair<>("", line); } - final Matcher m = linePattern.matcher(line); - boolean matches = false; - try { - matches = line.contains("#") && m.matches(); - } catch (StackOverflowError e) { // Probably a very long line - handleNodeStackOverflow(e, line); + + // idea: find first # that is not within a string or variable name. Use state machine to determine whether a # is a comment or not. + int length = line.length(); + StringBuilder finalLine = new StringBuilder(line); + int numRemoved = 0; + SplitLineState state = SplitLineState.CODE; + SplitLineState previousState = SplitLineState.CODE; // stores the state prior to entering %, so it can be re-set when leaving + // find next " or % + for (int i = 0; i < length; i++) { + char c = line.charAt(i); + // check for things that can be escaped by doubling + if (c == '%' || c == '"' || c == '#') { + // skip if doubled (only skip ## outside of strings) + if ((c != '#' || state != SplitLineState.STRING) && i + 1 < length && line.charAt(i + 1) == c) { + if (c == '#') { // remove duplicate # + finalLine.deleteCharAt(i - numRemoved); + numRemoved++; + } + i++; + continue; + } + SplitLineState tmp = state; + state = SplitLineState.update(c, state, previousState); + if (state == SplitLineState.HALT) + return new NonNullPair<>(finalLine.substring(0, i - numRemoved), line.substring(i)); + // only update previous state when we go from !CODE -> CODE due to % + if (c == '%' && state == SplitLineState.CODE) + previousState = tmp; + } } - if (matches) - return new NonNullPair<>("" + m.group(1).replace("##", "#"), "" + m.group(2)); - return new NonNullPair<>("" + line.replace("##", "#"), ""); + return new NonNullPair<>(finalLine.toString(), ""); } /** - * Splits a line into value and comment. - *

- * Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## in the value are replaced by a single #. The comment is returned with a - * leading #, except if there is no comment in which case it will be the empty string. - * - * @param line - * @return A pair (value, comment). + * state machine:
+ * ": CODE -> STRING, STRING -> CODE, VARIABLE -> VARIABLE
+ * %: CODE -> PREVIOUS_STATE, STRING -> CODE, VARIABLE -> CODE
+ * {: CODE -> VARIABLE, STRING -> STRING, VARIABLE -> VARIABLE
+ * }: CODE -> CODE, STRING -> STRING, VARIABLE -> CODE
+ * #: CODE -> HALT, STRING -> STRING, VARIABLE -> HALT
+ * invalid characters simply return given state.
*/ - public static NonNullPair splitLine(String line) { - return splitLine(line, new AtomicBoolean(false)); + private enum SplitLineState { + HALT, + CODE, + STRING, + VARIABLE; + + /** + * Updates the state given a character input. + * @param c character input. '"', '%', '{', '}', and '#' are valid. + * @param state the current state of the machine + * @param previousState the state of the machine when it last entered a % CODE % section + * @return the new state of the machine + */ + private static SplitLineState update(char c, SplitLineState state, SplitLineState previousState) { + if (state == HALT) + return HALT; + + switch (c) { + case '%': + if (state == CODE) + return previousState; + return CODE; + case '"': + switch (state) { + case CODE: + return STRING; + case STRING: + return CODE; + default: + return state; + } + case '{': + if (state == STRING) + return STRING; + return VARIABLE; + case '}': + if (state == STRING) + return STRING; + return CODE; + case '#': + if (state == STRING) + return STRING; + return HALT; + } + return state; + } } static void handleNodeStackOverflow(StackOverflowError e, String line) { Node n = SkriptLogger.getNode(); - SkriptLogger.setNode(null); // Avoid duplicating the which node error occurred in paranthesis on every error message + SkriptLogger.setNode(null); // Avoid duplicating the which node error occurred in parentheses on every error message Skript.error("There was a StackOverFlowError occurred when loading a node. This maybe from your scripts, aliases or Skript configuration."); Skript.error("Please make your script lines shorter! Do NOT report this to SkriptLang unless it occurs with a short script line or built-in aliases!"); @@ -214,12 +290,43 @@ protected String getIndentation() { abstract String save_i(); public final String save() { - return getIndentation() + save_i().replace("#", "##") + comment; + return getIndentation() + escapeUnquotedHashtags(save_i()) + comment; } public void save(final PrintWriter w) { w.println(save()); } + + private static String escapeUnquotedHashtags(String input) { + int length = input.length(); + StringBuilder output = new StringBuilder(input); + int numAdded = 0; + SplitLineState state = SplitLineState.CODE; + SplitLineState previousState = SplitLineState.CODE; + // find next " or % + for (int i = 0; i < length; i++) { + char c = input.charAt(i); + // check for things that can be escaped by doubling + if (c == '%' || c == '"' || c == '#') { + // escaped #s outside of strings + if (c == '#' && state != SplitLineState.STRING) { + output.insert(i + numAdded, "#"); + numAdded++; + continue; + } + // skip if doubled (not #s) + if (i + 1 < length && input.charAt(i + 1) == c) { + i++; + continue; + } + SplitLineState tmp = state; + state = SplitLineState.update(c, state, previousState); + previousState = tmp; + } + } + return output.toString(); + } + @Nullable public SectionNode getParent() { diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index e882057cebd..d79b1e65320 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -36,6 +36,9 @@ import com.google.common.collect.Lists; import com.google.common.io.Files; import org.apache.commons.lang.StringUtils; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockCanBuildEvent; import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.entry.EntryData; import org.skriptlang.skript.lang.entry.EntryValidator; @@ -481,6 +484,9 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu } desc = desc.replace("${element.id}", ID); + // Cancellable + desc = handleIf(desc, "${if cancellable}", false); + // Events Events events = c.getAnnotation(Events.class); desc = handleIf(desc, "${if events}", events != null); @@ -603,6 +609,17 @@ private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable String[] keywords = info.getKeywords(); desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords)); + // Cancellable + boolean cancellable = false; + for (Class event : info.events) { + if (Cancellable.class.isAssignableFrom(event) || BlockCanBuildEvent.class.isAssignableFrom(event)) { + cancellable = true; // let's assume all are cancellable otherwise EffCancelEvent would do the rest in action + break; + } + } + desc = handleIf(desc, "${if cancellable}", cancellable); + desc = desc.replace("${element.cancellable}", cancellable ? "Yes" : ""); // if not cancellable the section is hidden + // Documentation ID String ID = info.getDocumentationID() != null ? info.getDocumentationID() : info.getId(); // Fix duplicated IDs @@ -719,6 +736,9 @@ private String generateClass(String descTemp, ClassInfo info, @Nullable Strin } desc = desc.replace("${element.id}", ID); + // Cancellable + desc = handleIf(desc, "${if cancellable}", false); + // Events Events events = c.getAnnotation(Events.class); desc = handleIf(desc, "${if events}", events != null); @@ -821,6 +841,9 @@ private String generateFunction(String descTemp, JavaFunction info) { // Documentation ID desc = desc.replace("${element.id}", info.getName()); + // Cancellable + desc = handleIf(desc, "${if cancellable}", false); + // Events desc = handleIf(desc, "${if events}", false); // Functions do not require events nor plugins (at time writing this) diff --git a/src/main/java/ch/njol/skript/effects/EffBan.java b/src/main/java/ch/njol/skript/effects/EffBan.java index 1e674e23ded..88f815c96df 100644 --- a/src/main/java/ch/njol/skript/effects/EffBan.java +++ b/src/main/java/ch/njol/skript/effects/EffBan.java @@ -26,7 +26,6 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; @@ -38,6 +37,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; +import org.jetbrains.annotations.Nullable; @Name("Ban") @Description({"Bans or unbans a player or an IP address.", @@ -46,19 +46,23 @@ "We recommend that you test your scripts so that no accidental permanent bans are applied.", "", "Note that banning people does not kick them from the server.", - "Consider using the kick effect after applying a ban."}) + "You can optionally use 'and kick' or consider using the kick effect after applying a ban."}) @Examples({"unban player", "ban \"127.0.0.1\"", "IP-ban the player because \"he is an idiot\"", - "ban player due to \"inappropriate language\" for 2 days"}) -@Since("1.4, 2.1.1 (ban reason), 2.5 (timespan)") + "ban player due to \"inappropriate language\" for 2 days", + "ban and kick player due to \"inappropriate language\" for 2 days"}) +@Since("1.4, 2.1.1 (ban reason), 2.5 (timespan), 2.9.0 (kick)") public class EffBan extends Effect { static { Skript.registerEffect(EffBan.class, - "ban %strings/offlineplayers% [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", "unban %strings/offlineplayers%", - "ban %players% by IP [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", "unban %players% by IP", - "IP(-| )ban %players% [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", "(IP(-| )unban|un[-]IP[-]ban) %players%"); + "ban [kick:and kick] %strings/offlineplayers% [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", + "unban %strings/offlineplayers%", + "ban [kick:and kick] %players% by IP [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", + "unban %players% by IP", + "IP(-| )ban [kick:and kick] %players% [(by reason of|because [of]|on account of|due to) %-string%] [for %-timespan%]", + "(IP(-| )unban|un[-]IP[-]ban) %players%"); } @SuppressWarnings("null") @@ -70,6 +74,7 @@ public class EffBan extends Effect { private boolean ban; private boolean ipBan; + private boolean kick; @SuppressWarnings({"null", "unchecked"}) @Override @@ -79,6 +84,7 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final expires = exprs.length > 1 ? (Expression) exprs[2] : null; ban = matchedPattern % 2 == 0; ipBan = matchedPattern >= 2; + kick = parseResult.hasTag("kick"); return true; } @@ -91,8 +97,9 @@ protected void execute(final Event e) { final String source = "Skript ban effect"; for (final Object o : players.getArray(e)) { if (o instanceof Player) { + Player player = (Player) o; if (ipBan) { - InetSocketAddress addr = ((Player) o).getAddress(); + InetSocketAddress addr = player.getAddress(); if (addr == null) return; // Can't ban unknown IP final String ip = addr.getAddress().getHostAddress(); @@ -102,10 +109,12 @@ protected void execute(final Event e) { Bukkit.getBanList(BanList.Type.IP).pardon(ip); } else { if (ban) - Bukkit.getBanList(BanList.Type.NAME).addBan(((Player) o).getName(), reason, expires, source); // FIXME [UUID] ban UUID + Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), reason, expires, source); // FIXME [UUID] ban UUID else - Bukkit.getBanList(BanList.Type.NAME).pardon(((Player) o).getName()); + Bukkit.getBanList(BanList.Type.NAME).pardon(player.getName()); } + if (kick) + player.kickPlayer(reason); } else if (o instanceof OfflinePlayer) { String name = ((OfflinePlayer) o).getName(); if (name == null) @@ -130,9 +139,13 @@ protected void execute(final Event e) { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return (ipBan ? "IP-" : "") + (ban ? "" : "un") + "ban " + players.toString(e, debug) + - (reason != null ? " on account of " + reason.toString(e, debug) : "") + (expires != null ? " for " + expires.toString(e, debug) : ""); + public String toString(final @Nullable Event event, final boolean debug) { + return (ipBan ? "IP-" : "") + + (this.ban ? "ban " : "unban ") + + (kick ? "and kick " : "") + + this.players.toString(event, debug) + + (this.reason != null ? " on account of " + this.reason.toString(event, debug) : "") + + (expires != null ? " for " + expires.toString(event, debug) : ""); } } diff --git a/src/main/java/ch/njol/skript/effects/EffBroadcast.java b/src/main/java/ch/njol/skript/effects/EffBroadcast.java index 5e2b8fbdefb..373565bfe6d 100644 --- a/src/main/java/ch/njol/skript/effects/EffBroadcast.java +++ b/src/main/java/ch/njol/skript/effects/EffBroadcast.java @@ -18,6 +18,13 @@ */ package ch.njol.skript.effects; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -31,19 +38,20 @@ import ch.njol.skript.lang.VariableString; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.LiteralUtils; +import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.Utils; import ch.njol.skript.util.chat.BungeeConverter; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; +import org.bukkit.event.server.BroadcastMessageEvent; +import org.jetbrains.annotations.Nullable; @Name("Broadcast") @Description("Broadcasts a message to the server.") @@ -54,6 +62,8 @@ @Since("1.0, 2.6 (broadcasting objects), 2.6.1 (using advanced formatting)") public class EffBroadcast extends Effect { + private static final Pattern HEX_PATTERN = Pattern.compile("(?i)&x((?:&\\p{XDigit}){6})"); + static { Skript.registerEffect(EffBroadcast.class, "broadcast %objects% [(to|in) %-worlds%]"); } @@ -65,8 +75,8 @@ public class EffBroadcast extends Effect { @Nullable private Expression worlds; - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { messageExpr = LiteralUtils.defendExpression(exprs[0]); messages = messageExpr instanceof ExpressionList ? @@ -74,37 +84,51 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye worlds = (Expression) exprs[1]; return LiteralUtils.canInitSafely(messageExpr); } - + + /** + * This effect will call {@link BroadcastMessageEvent} as of 2.9.0. + */ @Override @SuppressWarnings("deprecation") - public void execute(Event e) { + public void execute(Event event) { List receivers = new ArrayList<>(); if (worlds == null) { receivers.addAll(Bukkit.getOnlinePlayers()); receivers.add(Bukkit.getConsoleSender()); } else { - for (World world : worlds.getArray(e)) + for (World world : worlds.getArray(event)) receivers.addAll(world.getPlayers()); } for (Expression message : getMessages()) { if (message instanceof VariableString) { - BaseComponent[] components = BungeeConverter.convert(((VariableString) message).getMessageComponents(e)); + if (!dispatchEvent(getRawString(event, (VariableString) message), receivers)) + continue; + BaseComponent[] components = BungeeConverter.convert(((VariableString) message).getMessageComponents(event)); receivers.forEach(receiver -> receiver.spigot().sendMessage(components)); } else if (message instanceof ExprColoured && ((ExprColoured) message).isUnsafeFormat()) { // Manually marked as trusted - for (Object realMessage : message.getArray(e)) { + for (Object realMessage : message.getArray(event)) { + if (!dispatchEvent(Utils.replaceChatStyles((String) realMessage), receivers)) + continue; BaseComponent[] components = BungeeConverter.convert(ChatMessages.parse((String) realMessage)); receivers.forEach(receiver -> receiver.spigot().sendMessage(components)); } } else { - for (Object messageObject : message.getArray(e)) { + for (Object messageObject : message.getArray(event)) { String realMessage = messageObject instanceof String ? (String) messageObject : Classes.toString(messageObject); + if (!dispatchEvent(Utils.replaceChatStyles(realMessage), receivers)) + continue; receivers.forEach(receiver -> receiver.sendMessage(realMessage)); } } } } + @Override + public String toString(@Nullable Event event, boolean debug) { + return "broadcast " + messageExpr.toString(event, debug) + (worlds == null ? "" : " to " + worlds.toString(event, debug)); + } + private Expression[] getMessages() { if (messageExpr instanceof ExpressionList && !messageExpr.getAnd()) { return new Expression[] {CollectionUtils.getRandom(messages)}; @@ -112,9 +136,35 @@ private Expression[] getMessages() { return messages; } - @Override - public String toString(@Nullable Event e, boolean debug) { - return "broadcast " + messageExpr.toString(e, debug) + (worlds == null ? "" : " to " + worlds.toString(e, debug)); + /** + * Manually calls a {@link BroadcastMessageEvent}. + * @param message the message + * @return true if the dispatched event does not get cancelled + */ + @SuppressWarnings("deprecation") + private static boolean dispatchEvent(String message, List receivers) { + Set recipients = Collections.unmodifiableSet(new HashSet<>(receivers)); + BroadcastMessageEvent broadcastEvent; + if (Skript.isRunningMinecraft(1, 14)) { + broadcastEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients); + } else { + broadcastEvent = new BroadcastMessageEvent(message, recipients); + } + Bukkit.getPluginManager().callEvent(broadcastEvent); + return !broadcastEvent.isCancelled(); } - + + @Nullable + private static String getRawString(Event event, Expression string) { + if (string instanceof VariableString) + return ((VariableString) string).toUnformattedString(event); + String rawString = string.getSingle(event); + rawString = SkriptColor.replaceColorChar(rawString); + if (rawString.toLowerCase().contains("&x")) { + rawString = StringUtils.replaceAll(rawString, HEX_PATTERN, matchResult -> + "<#" + matchResult.group(1).replace("&", "") + '>'); + } + return rawString; + } + } diff --git a/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java b/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java index cce18cb3cd9..86b37d2c88e 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java +++ b/src/main/java/ch/njol/skript/effects/EffEnforceWhitelist.java @@ -48,7 +48,7 @@ "enforce the whitelist", "unenforce the whitelist" }) -@Since("INSERT VERSION") +@Since("2.9.0") @RequiredPlugins("MC 1.17+") public class EffEnforceWhitelist extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index c66a5a1d2fd..baa7334b160 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -18,7 +18,9 @@ */ package ch.njol.skript.effects; +import ch.njol.skript.aliases.ItemData; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.ChestedHorse; import org.bukkit.entity.LivingEntity; @@ -93,13 +95,58 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye private static final boolean SUPPORTS_STEERABLE = Skript.classExists("org.bukkit.entity.Steerable"); - private static final ItemType CHESTPLATE = Aliases.javaItemType("chestplate"); - private static final ItemType LEGGINGS = Aliases.javaItemType("leggings"); - private static final ItemType BOOTS = Aliases.javaItemType("boots"); - private static final ItemType HORSE_ARMOR = Aliases.javaItemType("horse armor"); - private static final ItemType SADDLE = Aliases.javaItemType("saddle"); - private static final ItemType CHEST = Aliases.javaItemType("chest"); - private static final ItemType CARPET = Aliases.javaItemType("carpet"); + private static ItemType CHESTPLATE; + private static ItemType LEGGINGS; + private static ItemType BOOTS; + private static ItemType CARPET; + private static final ItemType HORSE_ARMOR = new ItemType(Material.IRON_HORSE_ARMOR, Material.GOLDEN_HORSE_ARMOR, Material.DIAMOND_HORSE_ARMOR); + private static final ItemType SADDLE = new ItemType(Material.SADDLE); + private static final ItemType CHEST = new ItemType(Material.CHEST); + + static { + boolean usesWoolCarpetTag = Skript.fieldExists(Tag.class, "WOOL_CARPET"); + CARPET = new ItemType(usesWoolCarpetTag ? Tag.WOOL_CARPETS : Tag.CARPETS); + // added in 1.20.6 + if (Skript.fieldExists(Tag.class, "ITEM_CHEST_ARMOR")) { + CHESTPLATE = new ItemType(Tag.ITEMS_CHEST_ARMOR); + LEGGINGS = new ItemType(Tag.ITEMS_LEG_ARMOR); + BOOTS = new ItemType(Tag.ITEMS_FOOT_ARMOR); + } else { + CHESTPLATE = new ItemType( + Material.LEATHER_CHESTPLATE, + Material.CHAINMAIL_CHESTPLATE, + Material.GOLDEN_CHESTPLATE, + Material.IRON_CHESTPLATE, + Material.DIAMOND_CHESTPLATE, + Material.ELYTRA + ); + + LEGGINGS = new ItemType( + Material.LEATHER_LEGGINGS, + Material.CHAINMAIL_LEGGINGS, + Material.GOLDEN_LEGGINGS, + Material.IRON_LEGGINGS, + Material.DIAMOND_LEGGINGS + ); + + BOOTS = new ItemType( + Material.LEATHER_BOOTS, + Material.CHAINMAIL_BOOTS, + Material.GOLDEN_BOOTS, + Material.IRON_BOOTS, + Material.DIAMOND_BOOTS + ); + + // netherite + if (Skript.isRunningMinecraft(1,16)) { + CHESTPLATE.add(new ItemData(Material.NETHERITE_CHESTPLATE)); + LEGGINGS.add(new ItemData(Material.NETHERITE_LEGGINGS)); + BOOTS.add(new ItemData(Material.NETHERITE_BOOTS)); + } + } + } + + private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET}; diff --git a/src/main/java/ch/njol/skript/effects/EffFireResistant.java b/src/main/java/ch/njol/skript/effects/EffFireResistant.java index 6a7bfb79f89..c5044e376fe 100644 --- a/src/main/java/ch/njol/skript/effects/EffFireResistant.java +++ b/src/main/java/ch/njol/skript/effects/EffFireResistant.java @@ -40,7 +40,7 @@ "make {_items::*} not resistant to fire" }) @RequiredPlugins("Spigot 1.20.5+") -@Since("INSERT VERSION") +@Since("2.9.0") public class EffFireResistant extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffLog.java b/src/main/java/ch/njol/skript/effects/EffLog.java index bce9d3bae76..aaa787d130f 100644 --- a/src/main/java/ch/njol/skript/effects/EffLog.java +++ b/src/main/java/ch/njol/skript/effects/EffLog.java @@ -46,28 +46,31 @@ import ch.njol.util.Closeable; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Log") @Description({"Writes text into a .log file. Skript will write these files to /plugins/Skript/logs.", "NB: Using 'server.log' as the log file will write to the default server log. Omitting the log file altogether will log the message as '[Skript] [<script>.sk] <message>' in the server log."}) -@Examples({"on place of TNT:", - " log \"%player% placed TNT in %world% at %location of block%\" to \"tnt/placement.log\""}) -@Since("2.0") +@Examples({ + "on join:", + "\tlog \"%player% has just joined the server!\"", + "on world change:", + "\tlog \"Someone just went to %event-world%!\" to file \"worldlog/worlds.log\"", + "on command:", + "\tlog \"%player% just executed %full command%!\" to file \"server/commands.log\" with a severity of warning" +}) +@Since("2.0, 2.9.0 (severities)") public class EffLog extends Effect { static { - Skript.registerEffect(EffLog.class, "log %strings% [(to|in) [file[s]] %-strings%]"); + Skript.registerEffect(EffLog.class, "log %strings% [(to|in) [file[s]] %-strings%] [with [the|a] severity [of] (1:warning|2:severe)]"); } - private final static File logsFolder = new File(Skript.getInstance().getDataFolder(), "logs"); + private static final File logsFolder = new File(Skript.getInstance().getDataFolder(), "logs"); final static HashMap writers = new HashMap<>(); static { Skript.closeOnDisable(new Closeable() { @Override public void close() { - for (final PrintWriter pw : writers.values()) + for (PrintWriter pw : writers.values()) pw.close(); } }); @@ -77,42 +80,55 @@ public void close() { private Expression messages; @Nullable private Expression files; - + + private Level logLevel = Level.INFO; + private static String getLogPrefix(Level logLevel) { + String timestamp = SkriptConfig.formatDate(System.currentTimeMillis()); + if (logLevel == Level.INFO) + return "[" + timestamp + "]"; + return "[" + timestamp + " " + logLevel + "]"; + } + @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { messages = (Expression) exprs[0]; files = (Expression) exprs[1]; + if (parser.mark == 1) { + logLevel = Level.WARNING; + } else if (parser.mark == 2) { + logLevel = Level.SEVERE; + } return true; } @SuppressWarnings("resource") @Override - protected void execute(final Event e) { - for (final String message : messages.getArray(e)) { + protected void execute(Event event) { + for (String message : messages.getArray(event)) { if (files != null) { - for (String s : files.getArray(e)) { - s = s.toLowerCase(Locale.ENGLISH); - if (!s.endsWith(".log")) - s += ".log"; - if (s.equals("server.log")) { - SkriptLogger.LOGGER.log(Level.INFO, message); + for (String logFile : files.getArray(event)) { + logFile = logFile.toLowerCase(Locale.ENGLISH); + if (!logFile.endsWith(".log")) + logFile += ".log"; + if (logFile.equals("server.log")) { + SkriptLogger.LOGGER.log(logLevel, message); continue; } - PrintWriter w = writers.get(s); - if (w == null) { - final File f = new File(logsFolder, s); // REMIND what if s contains '..'? + PrintWriter logWriter = writers.get(logFile); + if (logWriter == null) { + File logFolder = new File(logsFolder, logFile); // REMIND what if logFile contains '..'? try { - f.getParentFile().mkdirs(); - w = new PrintWriter(new BufferedWriter(new FileWriter(f, true))); - writers.put(s, w); - } catch (final IOException ex) { - Skript.error("Cannot write to log file '" + s + "' (" + f.getPath() + "): " + ExceptionUtils.toString(ex)); + logFolder.getParentFile().mkdirs(); + logWriter = new PrintWriter(new BufferedWriter(new FileWriter(logFolder, true))); + writers.put(logFile, logWriter); + } catch (IOException ex) { + Skript.error("Cannot write to log file '" + logFile + "' (" + logFolder.getPath() + "): " + ExceptionUtils.toString(ex)); return; } } - w.println("[" + SkriptConfig.formatDate(System.currentTimeMillis()) + "] " + message); - w.flush(); + logWriter.println(getLogPrefix(logLevel) + " " + message); + logWriter.flush(); } } else { Trigger t = getTrigger(); @@ -122,13 +138,15 @@ protected void execute(final Event e) { if (script != null) scriptName = script.getConfig().getFileName(); } - Skript.info("[" + scriptName + "] " + message); + SkriptLogger.LOGGER.log(logLevel, "[" + scriptName + "] " + message); } } } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "log " + messages.toString(e, debug) + (files != null ? " to " + files.toString(e, debug) : ""); + public String toString(@Nullable Event event, boolean debug) { + return "log " + messages.toString(event, debug) + + (files != null ? " to " + files.toString(event, debug) : "") + + (logLevel != Level.INFO ? "with severity " + logLevel.toString().toLowerCase(Locale.ENGLISH) : ""); } } diff --git a/src/main/java/ch/njol/skript/effects/EffOpenBook.java b/src/main/java/ch/njol/skript/effects/EffOpenBook.java index 5b96114818b..95d82caf27e 100644 --- a/src/main/java/ch/njol/skript/effects/EffOpenBook.java +++ b/src/main/java/ch/njol/skript/effects/EffOpenBook.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.effects; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; @@ -49,8 +50,6 @@ public class EffOpenBook extends Effect { } } - private static final ItemType bookItemType = Aliases.javaItemType("written book"); - @SuppressWarnings("null") private Expression book; @SuppressWarnings("null") @@ -69,7 +68,7 @@ protected void execute(final Event e) { ItemType itemType = book.getSingle(e); if (itemType != null) { ItemStack itemStack = itemType.getRandom(); - if (itemStack != null && bookItemType.isOfType(itemStack)) { + if (itemStack != null && itemStack.getType() == Material.WRITTEN_BOOK) { for (Player player : players.getArray(e)) { player.openBook(itemStack); } diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index 2d0d5cc0e5b..686f7334671 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -19,112 +19,170 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.AdventureSoundReceiver; +import ch.njol.skript.bukkitutil.AdventureSoundReceiver.AdventureEmitterSoundReceiver; +import ch.njol.skript.bukkitutil.AdventureSoundReceiver.AdventureEntitySoundReceiver; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; 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.util.Kleenean; import org.bukkit.Location; +import org.bukkit.NamespacedKey; import org.bukkit.Sound; import org.bukkit.SoundCategory; import org.bukkit.World; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Locale; +import java.util.OptionalLong; +import java.util.regex.Matcher; import java.util.regex.Pattern; @Name("Play Sound") -@Description({"Plays a sound at given location for everyone or just for given players, or plays a sound to specified players. " + - "Both Minecraft sound names and " + - "Spigot sound names " + - "are supported. Playing resource pack sounds are supported too. The sound category is 'master' by default. ", - "", - "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself."}) +@Description({ + "Plays a sound at given location for everyone or just for given players, or plays a sound to specified players. " + + "Both Minecraft sound names and " + + "Spigot sound names " + + "are supported. Playing resource pack sounds are supported too. The sound category is 'master' by default. ", + "", + "When running 1.18+, playing a sound from an entity directly will result in the sound coming from said entity, even while moving.", + "If the sound is custom, a location emitter will follow the entity. Do note that pitch and volume ", + "are reflected based on the entity, and Minecraft may not use the values from this syntax.", + "", + "If using Paper 1.19.4+ or Adventure API 4.12.0+ you can utilize sound seeds. Minecraft sometimes have a set of sounds under one sound ID ", + "that will randomly play, to counter this, you can directly state which seed to use.", + "", + "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself.", +}) @Examples({ "play sound \"block.note_block.pling\" # It is block.note.pling in 1.12.2", "play sound \"entity.experience_orb.pickup\" with volume 0.5 to the player", - "play sound \"custom.music.1\" in jukebox category at {speakerBlock}" + "play sound \"custom.music.1\" in jukebox category at {speakerBlock}", + "play sound \"BLOCK_AMETHYST_BLOCK_RESONATE\" with seed 1 on target entity for the player #1.20.1+" }) -@Since("2.2-dev28, 2.4 (sound categories)") +@RequiredPlugins("Minecraft 1.18.1+ (entity emitters), Paper 1.19.4+ or Adventure API 4.12.0+ (sound seed)") +@Since("2.2-dev28, 2.4 (sound categories), 2.9.0 (sound seed & entity emitter)") public class EffPlaySound extends Effect { - private static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?[a-z0-9/._-]+"); - + private static final boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder"); + private static final boolean PLAYER_ENTITY_EMITTER = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); + private static final boolean WORLD_ENTITY_EMITTER = Skript.methodExists(World.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); + public static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?([a-z0-9/._-]+)"); + static { + String additional = ""; + if (ADVENTURE_API) + additional = "[[with] seed %-number%] "; + String emitterTypes = "locations"; + if (PLAYER_ENTITY_EMITTER) + emitterTypes += "/entities"; Skript.registerEffect(EffPlaySound.class, - "play sound[s] %strings% [(in|from) %-soundcategory%] " + - "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", - "play sound[s] %strings% [(in|from) %-soundcategory%] " + - "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]" + "play sound[s] %strings% " + additional + "[(in|from) %-soundcategory%] " + + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] (at|on|from) %" + emitterTypes + "% [(to|for) %-players%]", + "play sound[s] %strings% " + additional + "[(in|from) %-soundcategory%] " + + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|on|from) %-" + emitterTypes + "%]" ); } @SuppressWarnings("NotNullFieldNotInitialized") private Expression sounds; + @Nullable private Expression category; + + @Nullable + private Expression players; + @Nullable private Expression volume; + @Nullable private Expression pitch; + @Nullable - private Expression locations; + private Expression seed; + @Nullable - private Expression players; + private Expression emitters; @Override @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { sounds = (Expression) exprs[0]; - category = (Expression) exprs[1]; - volume = (Expression) exprs[2]; - pitch = (Expression) exprs[3]; + int index = 1; + if (ADVENTURE_API) + seed = (Expression) exprs[index++]; + category = (Expression) exprs[index++]; + volume = (Expression) exprs[index++]; + pitch = (Expression) exprs[index++]; if (matchedPattern == 0) { - locations = (Expression) exprs[4]; - players = (Expression) exprs[5]; + emitters = exprs[index++]; + players = (Expression) exprs[index]; } else { - players = (Expression) exprs[4]; - locations = (Expression) exprs[5]; + players = (Expression) exprs[index++]; + emitters = exprs[index]; } - return true; } @Override protected void execute(Event event) { + OptionalLong seed = OptionalLong.empty(); + if (this.seed != null) { + Number number = this.seed.getSingle(event); + if (number != null) + seed = OptionalLong.of(number.longValue()); + } SoundCategory category = this.category == null ? SoundCategory.MASTER : this.category.getOptionalSingle(event) - .orElse(SoundCategory.MASTER); + .orElse(SoundCategory.MASTER); float volume = this.volume == null ? 1 : this.volume.getOptionalSingle(event) - .orElse(1) - .floatValue(); + .orElse(1) + .floatValue(); float pitch = this.pitch == null ? 1 : this.pitch.getOptionalSingle(event) - .orElse(1) - .floatValue(); - + .orElse(1) + .floatValue(); + if (players != null) { - if (locations == null) { + if (emitters == null) { for (Player player : players.getArray(event)) { - SoundReceiver.play(Player::playSound, Player::playSound, player, - player.getLocation(), sounds.getArray(event), category, volume, pitch); + play(PLAYER_ENTITY_EMITTER ? Player::playSound : null, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, + player, player.getLocation(), sounds.getArray(event), category, volume, pitch, seed); } } else { for (Player player : players.getArray(event)) { - for (Location location : locations.getArray(event)) { - SoundReceiver.play(Player::playSound, Player::playSound, player, - location, sounds.getArray(event), category, volume, pitch); + for (Object emitter : emitters.getArray(event)) { + if (emitter instanceof Entity && PLAYER_ENTITY_EMITTER) { + Entity entity = (Entity) emitter; + play(Player::playSound, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, + player, entity, sounds.getArray(event), category, volume, pitch, seed); + } else if (emitter instanceof Location) { + Location location = (Location) emitter; + play(PLAYER_ENTITY_EMITTER ? Player::playSound : null, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, + player, location, sounds.getArray(event), category, volume, pitch, seed); + } } } } - } else if (locations != null) { - for (Location location : locations.getArray(event)) { - SoundReceiver.play(World::playSound, World::playSound, location.getWorld(), - location, sounds.getArray(event), category, volume, pitch); + } else if (emitters != null) { + for (Object emitter : emitters.getArray(event)) { + if (emitter instanceof Entity && WORLD_ENTITY_EMITTER) { + Entity entity = (Entity) emitter; + play(World::playSound, World::playSound, ADVENTURE_API ? World::playSound : null, ADVENTURE_API ? World::playSound : null, + entity.getWorld(), entity, sounds.getArray(event), category, volume, pitch, seed); + } else if (emitter instanceof Location) { + Location location = (Location) emitter; + play(WORLD_ENTITY_EMITTER ? World::playSound : null, World::playSound, ADVENTURE_API ? World::playSound : null, ADVENTURE_API ? World::playSound : null, + location.getWorld(), location, sounds.getArray(event), category, volume, pitch, seed); + } } } } @@ -132,53 +190,85 @@ protected void execute(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { StringBuilder builder = new StringBuilder() - .append("play sound ") - .append(sounds.toString(event, debug)); - + .append("play sound ") + .append(sounds.toString(event, debug)); + + if (seed != null) + builder.append(" with seed ").append(seed.toString(event, debug)); if (category != null) builder.append(" in ").append(category.toString(event, debug)); - if (volume != null) builder.append(" with volume ").append(volume.toString(event, debug)); - if (pitch != null) builder.append(" with pitch ").append(pitch.toString(event, debug)); - - if (locations != null) - builder.append(" at ").append(locations.toString(event, debug)); - + if (emitters != null) + builder.append(" from ").append(emitters.toString(event, debug)); if (players != null) builder.append(" to ").append(players.toString(event, debug)); return builder.toString(); } - + + private void play(@Nullable SoundReceiver entityReceiver, + @NotNull SoundReceiver locationReceiver, + @Nullable AdventureEmitterSoundReceiver adventureLocationReceiver, + @Nullable AdventureEntitySoundReceiver adventureEmitterReceiver, + @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, + @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed) { + if (!ADVENTURE_API || adventureLocationReceiver == null || adventureEmitterReceiver == null) { + SoundReceiver.play(entityReceiver, locationReceiver, receiver, emitter, sounds, category, volume, pitch, seed); + return; + } + AdventureSoundReceiver.play(adventureLocationReceiver, adventureEmitterReceiver, receiver, emitter, sounds, category, volume, pitch, seed); + } + @FunctionalInterface - private interface SoundReceiver { + private interface SoundReceiver { void play( - @NotNull T receiver, @NotNull Location location, @NotNull S sound, + @NotNull T receiver, @NotNull E emitter, @NotNull String sound, @NotNull SoundCategory category, float volume, float pitch ); - - static void play( - @NotNull SoundReceiver stringReceiver, - @NotNull SoundReceiver soundReceiver, - @NotNull T receiver, @NotNull Location location, @NotNull String[] sounds, - @NotNull SoundCategory category, float volume, float pitch - ) { + + static void play( + @Nullable SoundReceiver entityReceiver, + @NotNull SoundReceiver locationReceiver, + @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, + @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed + ) { for (String sound : sounds) { + NamespacedKey key = null; try { Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - soundReceiver.play(receiver, location, enumSound, category, volume, pitch); - continue; - } catch (IllegalArgumentException ignored) {} - - sound = sound.toLowerCase(Locale.ENGLISH); - if (!KEY_PATTERN.matcher(sound).matches()) + key = enumSound.getKey(); + } catch (IllegalArgumentException alternative) { + sound = sound.toLowerCase(Locale.ENGLISH); + Matcher keyMatcher = KEY_PATTERN.matcher(sound); + if (!keyMatcher.matches()) + continue; + try { + String namespace = keyMatcher.group(1); + String keyValue = keyMatcher.group(2); + if (namespace == null) { + key = NamespacedKey.minecraft(keyValue); + } else { + namespace = namespace.substring(0, namespace.length() - 1); + key = new NamespacedKey(namespace, keyValue); + } + } catch (IllegalArgumentException argument) { + // The user input invalid characters + } + } + + if (key == null) continue; - - stringReceiver.play(receiver, location, sound, category, volume, pitch); + if (emitter instanceof Location) { + locationReceiver.play(receiver, (Location) emitter, key.getKey(), category, volume, pitch); + } else if (emitter instanceof Entity && entityReceiver != null) { + entityReceiver.play(receiver, (Entity) emitter, key.getKey(), category, volume, pitch); + } + return; } } } + } diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index f6a1fd5bad8..bf163106851 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -129,9 +129,10 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) if (new ItemType(itemStack).isSimilar(needle)) { ItemStack newItemStack = ((ItemType) replacement).getRandom(); - newItemStack.setAmount(itemStack.getAmount()); - - inv.setItem(slot, newItemStack); + if (newItemStack != null) { + newItemStack.setAmount(itemStack.getAmount()); + inv.setItem(slot, newItemStack); + } } } } diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 21ce3d4d475..7c4025b5d7a 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -19,7 +19,6 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; -import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -110,7 +109,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected TriggerItem walk(Event event) { debug(event, false); //noinspection rawtypes,unchecked - ((ReturnHandler) handler).returnValues(value.getArray(event)); + ((ReturnHandler) handler).returnValues(event, value); TriggerSection parent = getParent(); while (parent != null && parent != handler) { diff --git a/src/main/java/ch/njol/skript/effects/EffRing.java b/src/main/java/ch/njol/skript/effects/EffRing.java index 8b66e1ce488..54ec99a97da 100644 --- a/src/main/java/ch/njol/skript/effects/EffRing.java +++ b/src/main/java/ch/njol/skript/effects/EffRing.java @@ -46,7 +46,7 @@ }) @Examples({"make player ring target-block"}) @RequiredPlugins("Spigot 1.19.4+") -@Since("INSERT VERSION") +@Since("2.9.0") public class EffRing extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffSort.java b/src/main/java/ch/njol/skript/effects/EffSort.java new file mode 100644 index 00000000000..19de3a87362 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffSort.java @@ -0,0 +1,167 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.ExprInput; +import ch.njol.skript.expressions.ExprSortedList; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.InputSource; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.util.Kleenean; +import ch.njol.util.Pair; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +@Name("Sort") +@Description({ + "Sorts a list variable using either the natural ordering of the contents or the results of the given expression.", + "Be warned, this will overwrite the indices of the list variable." +}) +@Examples({ + "set {_words::*} to \"pineapple\", \"banana\", \"yoghurt\", and \"apple\"", + "sort {_words::*} # alphabetical sort", + "sort {_words::*} by length of input # shortest to longest", + "sort {_words::*} based on {tastiness::%input%} # sort based on custom value" +}) +@Since("2.9.0") +@Keywords("input") +public class EffSort extends Effect implements InputSource { + + static { + Skript.registerEffect(EffSort.class, "sort %~objects% [(by|based on) <.+>]"); + if (!ParserInstance.isRegistered(InputData.class)) + ParserInstance.registerData(InputData.class, InputData::new); + } + + @Nullable + private Expression mappingExpr; + @Nullable + private String unparsedExpression; + private Variable unsortedObjects; + + private Set> dependentInputs = new HashSet<>(); + + @Nullable + private Object currentValue; + @UnknownNullability + private String currentIndex; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (expressions[0].isSingle() || !(expressions[0] instanceof Variable)) { + Skript.error("You can only sort list variables!"); + return false; + } + unsortedObjects = (Variable) expressions[0]; + + if (!parseResult.regexes.isEmpty()) { + unparsedExpression = parseResult.regexes.get(0).group(); + assert unparsedExpression != null; + InputData inputData = getParser().getData(InputData.class); + InputSource originalSource = inputData.getSource(); + inputData.setSource(this); + mappingExpr = new SkriptParser(unparsedExpression, SkriptParser.PARSE_EXPRESSIONS, ParseContext.DEFAULT) + .parseExpression(Object.class); + inputData.setSource(originalSource); + return mappingExpr != null && mappingExpr.isSingle(); + } + return true; + } + + @Override + protected void execute(Event event) { + Object[] sorted; + if (mappingExpr == null) { + try { + sorted = unsortedObjects.stream(event) + .sorted(ExprSortedList::compare) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return; + } + } else { + Map valueToMappedValue = new LinkedHashMap<>(); + for (Iterator> it = unsortedObjects.variablesIterator(event); it.hasNext(); ) { + Pair pair = it.next(); + currentIndex = pair.getKey(); + currentValue = pair.getValue(); + Object mappedValue = mappingExpr.getSingle(event); + if (mappedValue == null) + return; + valueToMappedValue.put(currentValue, mappedValue); + } + try { + sorted = valueToMappedValue.entrySet().stream() + .sorted(Map.Entry.comparingByValue(ExprSortedList::compare)) + .map(Map.Entry::getKey) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return; + } + } + + unsortedObjects.change(event, sorted, ChangeMode.SET); + } + + @Override + public Set> getDependentInputs() { + return dependentInputs; + } + + @Override + public @Nullable Object getCurrentValue() { + return currentValue; + } + + @Override + public boolean hasIndices() { + return true; + } + + @Override + public @UnknownNullability String getCurrentIndex() { + return currentIndex; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "sort" + unsortedObjects.toString(event, debug) + + (mappingExpr == null ? "" : " by " + mappingExpr.toString(event, debug)); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffTame.java b/src/main/java/ch/njol/skript/effects/EffTame.java new file mode 100644 index 00000000000..400f9e27d39 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffTame.java @@ -0,0 +1,54 @@ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Tameable; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Tame / Untame") +@Description("Tame a tameable entity (horse, parrot, cat, etc.).") +@Examples({ + "tame {_horse}", + "untame {_horse}" +}) +@Since("INSERT VERSION") +public class EffTame extends Effect { + + static { + Skript.registerEffect(EffTame.class, "[:un](tame|domesticate) %entities%"); + } + + private boolean tame; + private Expression entities; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + tame = !parseResult.hasTag("un"); + entities = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Entity entity : entities.getArray(event)) { + if (entity instanceof Tameable) + ((Tameable) entity).setTamed(tame); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (tame ? "tame " : "untame ") + entities.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffTeleport.java b/src/main/java/ch/njol/skript/effects/EffTeleport.java index 3ba06d7d150..990e1aa7ec1 100644 --- a/src/main/java/ch/njol/skript/effects/EffTeleport.java +++ b/src/main/java/ch/njol/skript/effects/EffTeleport.java @@ -19,7 +19,6 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; -import ch.njol.skript.sections.EffSecSpawn; import ch.njol.skript.sections.EffSecSpawn.SpawnEvent; import ch.njol.skript.bukkitutil.EntityUtils; import ch.njol.skript.doc.Description; @@ -31,7 +30,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.util.Direction; import ch.njol.skript.variables.Variables; @@ -53,7 +51,7 @@ "which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks." }) @Examples({ - "teleport the player to {homes.%player%}", + "teleport the player to {homes::%player%}", "teleport the attacker to the victim" }) @Since("1.0") @@ -101,6 +99,7 @@ protected TriggerItem walk(Event e) { Location loc = location.getSingle(e); if (loc == null) return next; + boolean unknownWorld = !loc.isWorldLoaded(); Entity[] entityArray = entities.getArray(e); // We have to fetch this before possible async execution to avoid async local variable access. if (entityArray.length == 0) @@ -108,11 +107,17 @@ protected TriggerItem walk(Event e) { if (!delayed) { if (e instanceof PlayerRespawnEvent && entityArray.length == 1 && entityArray[0].equals(((PlayerRespawnEvent) e).getPlayer())) { + if (unknownWorld) + return next; ((PlayerRespawnEvent) e).setRespawnLocation(loc); return next; } if (e instanceof PlayerMoveEvent && entityArray.length == 1 && entityArray[0].equals(((PlayerMoveEvent) e).getPlayer())) { + if (unknownWorld) { // we can approximate the world + loc = loc.clone(); + loc.setWorld(((PlayerMoveEvent) e).getFrom().getWorld()); + } ((PlayerMoveEvent) e).setTo(loc); return next; } @@ -125,6 +130,19 @@ protected TriggerItem walk(Event e) { 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]; + if (entity == null) + return next; + // assume it's a local teleport, use the first entity we find as a reference + loc = loc.clone(); + loc.setWorld(entity.getWorld()); + } else { + return next; // no entities = no chunk = nobody teleporting + } + } + final Location fixed = loc; Delay.addDelayedEvent(e); Object localVars = Variables.removeLocals(e); @@ -132,7 +150,7 @@ protected TriggerItem walk(Event e) { PaperLib.getChunkAtAsync(loc).thenAccept(chunk -> { // The following is now on the main thread for (Entity entity : entityArray) { - EntityUtils.teleport(entity, loc); + EntityUtils.teleport(entity, fixed); } // Re-set local variables diff --git a/src/main/java/ch/njol/skript/effects/EffTooltip.java b/src/main/java/ch/njol/skript/effects/EffTooltip.java new file mode 100644 index 00000000000..4a9ba7c4392 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffTooltip.java @@ -0,0 +1,95 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +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.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Item Tooltips") +@Description({ + "Show or hide the tooltip of an item.", + "If changing the 'entire' tooltip of an item, nothing will show up when a player hovers over it.", + "If changing the 'additional' tooltip, only specific parts (which change per item) will be hidden." +}) +@Examples({ + "hide the entire tooltip of player's tool", + "hide {_item}'s additional tool tip" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("2.9.0") +public class EffTooltip extends Effect { + + static { + if (Skript.methodExists(ItemMeta.class, "setHideTooltip", boolean.class)) { // this method was added in the same version as the additional tooltip item flag + Skript.registerEffect(EffTooltip.class, + "(show|reveal|:hide) %itemtypes%'[s] [entire|:additional] tool[ ]tip", + "(show|reveal|:hide) [the] [entire|:additional] tool[ ]tip of %itemtypes%" + ); + } + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression items; + private boolean hide, entire; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression) exprs[0]; + hide = parseResult.hasTag("hide"); + entire = !parseResult.hasTag("additional"); + return true; + } + + @Override + protected void execute(Event event) { + for (ItemType item : items.getArray(event)) { + ItemMeta meta = item.getItemMeta(); + if (entire) { + meta.setHideTooltip(hide); + } else { + if (hide) { + meta.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + } else { + meta.removeItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + } + } + item.setItemMeta(meta); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (hide ? "hide" : "show") + " the " + (entire ? "entire" : "additional") + " tooltip of " + items.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/entity/BoatChestData.java b/src/main/java/ch/njol/skript/entity/BoatChestData.java index bb8d8506e23..499a7ed3e44 100644 --- a/src/main/java/ch/njol/skript/entity/BoatChestData.java +++ b/src/main/java/ch/njol/skript/entity/BoatChestData.java @@ -23,6 +23,7 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser; +import org.bukkit.Material; import org.bukkit.TreeSpecies; import org.bukkit.entity.ChestBoat; import org.bukkit.inventory.ItemStack; @@ -32,21 +33,8 @@ public class BoatChestData extends EntityData { - private static ItemType oakBoat = null; - private static ItemType spruceBoat = null; - private static ItemType birchBoat = null; - private static ItemType jungleBoat = null; - private static ItemType acaciaBoat = null; - private static ItemType darkOakBoat = null; - static { if (Skript.classExists("org.bukkit.entity.ChestBoat")) { - oakBoat = Aliases.javaItemType("oak chest boat"); - spruceBoat = Aliases.javaItemType("spruce chest boat"); - birchBoat = Aliases.javaItemType("birch chest boat"); - jungleBoat = Aliases.javaItemType("jungle chest boat"); - acaciaBoat = Aliases.javaItemType("acacia chest boat"); - darkOakBoat = Aliases.javaItemType("dark oak chest boat"); EntityData.register(BoatChestData.class, "chest boat", ChestBoat.class, 0, "chest boat", "any chest boat", "oak chest boat", "spruce chest boat", "birch chest boat", "jungle chest boat", "acacia chest boat", "dark oak chest boat"); @@ -120,22 +108,20 @@ public boolean isSupertypeOf(EntityData e) { } public boolean isOfItemType(ItemType itemType) { - if (itemType.getRandom() == null) - return false; int ordinal = -1; - ItemStack stack = itemType.getRandom(); - if (oakBoat.isOfType(stack)) + Material type = itemType.getMaterial(); + if (type == Material.OAK_CHEST_BOAT) ordinal = 0; - else if (spruceBoat.isOfType(stack)) + else if (type == Material.SPRUCE_CHEST_BOAT) ordinal = TreeSpecies.REDWOOD.ordinal(); - else if (birchBoat.isOfType(stack)) + else if (type == Material.BIRCH_CHEST_BOAT) ordinal = TreeSpecies.BIRCH.ordinal(); - else if (jungleBoat.isOfType(stack)) + else if (type == Material.JUNGLE_CHEST_BOAT) ordinal = TreeSpecies.JUNGLE.ordinal(); - else if (acaciaBoat.isOfType(stack)) + else if (type == Material.ACACIA_CHEST_BOAT) ordinal = TreeSpecies.ACACIA.ordinal(); - else if (darkOakBoat.isOfType(stack)) + else if (type == Material.DARK_OAK_CHEST_BOAT) ordinal = TreeSpecies.DARK_OAK.ordinal(); return hashCode_i() == ordinal + 2 || (matchedPattern + ordinal == 0) || ordinal == 0; } diff --git a/src/main/java/ch/njol/skript/entity/BoatData.java b/src/main/java/ch/njol/skript/entity/BoatData.java index dfeb1f22890..36bc23691d7 100644 --- a/src/main/java/ch/njol/skript/entity/BoatData.java +++ b/src/main/java/ch/njol/skript/entity/BoatData.java @@ -18,8 +18,10 @@ */ package ch.njol.skript.entity; +import java.lang.reflect.Method; import java.util.Random; +import org.bukkit.Material; import org.bukkit.TreeSpecies; import org.bukkit.entity.Boat; import org.bukkit.inventory.ItemStack; @@ -108,30 +110,21 @@ public boolean isSupertypeOf(EntityData e) { return false; } - private static final ItemType oakBoat = Aliases.javaItemType("oak boat"); - private static final ItemType spruceBoat = Aliases.javaItemType("spruce boat"); - private static final ItemType birchBoat = Aliases.javaItemType("birch boat"); - private static final ItemType jungleBoat = Aliases.javaItemType("jungle boat"); - private static final ItemType acaciaBoat = Aliases.javaItemType("acacia boat"); - private static final ItemType darkOakBoat = Aliases.javaItemType("dark oak boat"); - public boolean isOfItemType(ItemType i){ - if (i.getRandom() == null) - return false; int ordinal = -1; - - ItemStack stack = i.getRandom(); - if (oakBoat.isOfType(stack)) + + Material type = i.getMaterial(); + if (type == Material.OAK_BOAT) ordinal = 0; - else if (spruceBoat.isOfType(stack)) + else if (type == Material.SPRUCE_BOAT) ordinal = TreeSpecies.REDWOOD.ordinal(); - else if (birchBoat.isOfType(stack)) + else if (type == Material.BIRCH_BOAT) ordinal = TreeSpecies.BIRCH.ordinal(); - else if (jungleBoat.isOfType(stack)) + else if (type == Material.JUNGLE_BOAT) ordinal = TreeSpecies.JUNGLE.ordinal(); - else if (acaciaBoat.isOfType(stack)) + else if (type == Material.ACACIA_BOAT) ordinal = TreeSpecies.ACACIA.ordinal(); - else if (darkOakBoat.isOfType(stack)) + else if (type == Material.DARK_OAK_BOAT) ordinal = TreeSpecies.DARK_OAK.ordinal(); return hashCode_i() == ordinal + 2 || (matchedPattern + ordinal == 0) || ordinal == 0; diff --git a/src/main/java/ch/njol/skript/entity/CatData.java b/src/main/java/ch/njol/skript/entity/CatData.java index 20d338e39df..1c435133c8e 100644 --- a/src/main/java/ch/njol/skript/entity/CatData.java +++ b/src/main/java/ch/njol/skript/entity/CatData.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.entity; +import ch.njol.skript.registrations.Classes; +import com.google.common.collect.Iterators; import org.bukkit.entity.Cat; import org.eclipse.jdt.annotation.Nullable; @@ -26,12 +28,19 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.coll.CollectionUtils; +import java.util.Objects; + public class CatData extends EntityData { static { - if (Skript.classExists("org.bukkit.entity.Cat")) + if (Skript.classExists("org.bukkit.entity.Cat")) { EntityData.register(CatData.class, "cat", Cat.class, "cat"); + types = Iterators.toArray(Classes.getExactClassInfo(Cat.Type.class).getSupplier().get(), Cat.Type.class); + } } + + @SuppressWarnings("NotNullFieldNotInitialized") + private static Cat.Type[] types; private Cat.@Nullable Type race = null; @@ -42,7 +51,7 @@ protected boolean init(Literal[] exprs, int matchedPattern, ParseResult parse race = ((Literal) exprs[0]).getSingle(); return true; } - + @Override protected boolean init(@Nullable Class c, @Nullable Cat cat) { race = (cat == null) ? Cat.Type.TABBY : cat.getCatType(); @@ -51,7 +60,7 @@ protected boolean init(@Nullable Class c, @Nullable Cat cat) { @Override public void set(Cat entity) { - Cat.Type type = race != null ? race : CollectionUtils.getRandom(Cat.Type.values()); + Cat.Type type = race != null ? race : CollectionUtils.getRandom(types); assert type != null; entity.setCatType(type); } @@ -73,7 +82,7 @@ public EntityData getSuperType() { @Override protected int hashCode_i() { - return race != null ? race.hashCode() : 0; + return Objects.hashCode(race); } @Override diff --git a/src/main/java/ch/njol/skript/entity/DroppedItemData.java b/src/main/java/ch/njol/skript/entity/DroppedItemData.java index e9b136407c1..aa7b4a6b8a4 100644 --- a/src/main/java/ch/njol/skript/entity/DroppedItemData.java +++ b/src/main/java/ch/njol/skript/entity/DroppedItemData.java @@ -64,8 +64,15 @@ public DroppedItemData(ItemType @Nullable [] types) { @Override protected boolean init(Literal[] expressions, int matchedPattern, ParseResult parseResult) { - if (expressions.length > 0 && expressions[0] != null) + if (expressions.length > 0 && expressions[0] != null) { types = (ItemType[]) expressions[0].getAll(); + for (ItemType type : types) { + if (!type.getMaterial().isItem()) { + Skript.error("'" + type + "' cannot represent a dropped item"); + return false; + } + } + } return true; } @@ -97,6 +104,7 @@ public void set(final Item entity) { final ItemType t = CollectionUtils.getRandom(types); assert t != null; ItemStack stack = t.getItem().getRandom(); + assert stack != null; // should be true by init checks entity.setItemStack(stack); } @@ -159,6 +167,7 @@ public boolean canSpawn(@Nullable World world) { final ItemType itemType = CollectionUtils.getRandom(types); assert itemType != null; ItemStack stack = itemType.getItem().getRandom(); + assert stack != null; // should be true by init checks Item item; if (consumer == null) { diff --git a/src/main/java/ch/njol/skript/entity/FrogData.java b/src/main/java/ch/njol/skript/entity/FrogData.java index 116c781b326..8ce2d24f712 100644 --- a/src/main/java/ch/njol/skript/entity/FrogData.java +++ b/src/main/java/ch/njol/skript/entity/FrogData.java @@ -25,6 +25,8 @@ import org.bukkit.entity.Frog.Variant; import org.eclipse.jdt.annotation.Nullable; +import java.util.Objects; + public class FrogData extends EntityData { static { @@ -42,13 +44,28 @@ public FrogData() { public FrogData(@Nullable Variant variant) { this.variant = variant; - matchedPattern = variant != null ? variant.ordinal() + 1 : 0; + matchedPattern = 0; + if (variant == Variant.TEMPERATE) + matchedPattern = 1; + if (variant == Variant.WARM) + matchedPattern = 2; + if (variant == Variant.COLD) + matchedPattern = 3; } @Override protected boolean init(Literal[] exprs, int matchedPattern, SkriptParser.ParseResult parseResult) { - if (matchedPattern > 0) - variant = Variant.values()[matchedPattern - 1]; + switch (matchedPattern) { + case 1: + variant = Variant.TEMPERATE; + break; + case 2: + variant = Variant.WARM; + break; + case 3: + variant = Variant.COLD; + break; + } return true; } @@ -82,7 +99,7 @@ public EntityData getSuperType() { @Override protected int hashCode_i() { - return variant != null ? variant.hashCode() : 0; + return Objects.hashCode(variant); } @Override diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 407460d56c5..12d03199fbe 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -22,6 +22,7 @@ import java.util.function.Consumer; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; @@ -51,9 +52,9 @@ public class ThrownPotionData extends EntityData { @SuppressWarnings("removal") private static final Class LINGERING_POTION_ENTITY_CLASS = LINGERING_POTION_ENTITY_USED ? LingeringPotion.class : ThrownPotion.class; - private static final ItemType POTION = Aliases.javaItemType("potion"); - private static final ItemType SPLASH_POTION = Aliases.javaItemType("splash potion"); - private static final ItemType LINGER_POTION = Aliases.javaItemType("lingering potion"); + private static final Material POTION = Material.POTION; + private static final Material SPLASH_POTION = Material.SPLASH_POTION; + private static final Material LINGER_POTION = Material.LINGERING_POTION; @Nullable private ItemType[] types; @@ -63,18 +64,18 @@ protected boolean init(Literal[] exprs, int matchedPattern, ParseResult parse if (exprs.length > 0 && exprs[0] != null) { return (types = Converters.convert((ItemType[]) exprs[0].getAll(), ItemType.class, t -> { // If the itemtype is a potion, lets make it a splash potion (required by Bukkit) - if (t.isSupertypeOf(POTION)) { + if (t.getMaterial() == POTION) { ItemMeta meta = t.getItemMeta(); - ItemType itemType = SPLASH_POTION.clone(); + ItemType itemType = new ItemType(SPLASH_POTION); itemType.setItemMeta(meta); return itemType; - } else if (!t.isSupertypeOf(SPLASH_POTION ) && !t.isSupertypeOf(LINGER_POTION)) { + } else if (t.getMaterial() != SPLASH_POTION && t.getMaterial() != LINGER_POTION) { return null; } return t; })).length != 0; // no error message - other things can be thrown as well } else { - types = new ItemType[]{SPLASH_POTION.clone()}; + types = new ItemType[]{new ItemType(SPLASH_POTION)}; } return true; } @@ -109,7 +110,7 @@ protected boolean match(ThrownPotion entity) { if (i == null) return null; - Class thrownPotionClass = (Class) (LINGER_POTION.isOfType(i) ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); + Class thrownPotionClass = (Class) (i.getType() == LINGER_POTION ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); ThrownPotion potion; if (consumer != null) { potion = EntityData.spawn(location, thrownPotionClass, consumer); @@ -131,7 +132,7 @@ public void set(ThrownPotion entity) { ItemStack i = t.getRandom(); if (i == null) return; // Missing item, can't make thrown potion of it - if (LINGERING_POTION_ENTITY_USED && (LINGERING_POTION_ENTITY_CLASS.isInstance(entity) != LINGER_POTION.isOfType(i))) + if (LINGERING_POTION_ENTITY_USED && (LINGERING_POTION_ENTITY_CLASS.isInstance(entity) != (LINGER_POTION == i.getType()))) return; entity.setItem(i); } diff --git a/src/main/java/ch/njol/skript/entity/VillagerData.java b/src/main/java/ch/njol/skript/entity/VillagerData.java index 2a686ca6784..dfceb63f867 100644 --- a/src/main/java/ch/njol/skript/entity/VillagerData.java +++ b/src/main/java/ch/njol/skript/entity/VillagerData.java @@ -18,10 +18,13 @@ */ package ch.njol.skript.entity; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.bukkit.entity.Villager; import org.bukkit.entity.Villager.Profession; @@ -37,7 +40,7 @@ * @author Peter Güttinger */ public class VillagerData extends EntityData { - + /** * Professions can be for zombies also. These are the ones which are only * for villagers. @@ -49,25 +52,37 @@ public class VillagerData extends EntityData { // NORMAL(-1), FARMER(0), LIBRARIAN(1), PRIEST(2), BLACKSMITH(3), BUTCHER(4), NITWIT(5); Variables.yggdrasil.registerSingleClass(Profession.class, "Villager.Profession"); - + + professions = new ArrayList<>(); if (Skript.isRunningMinecraft(1, 14)) { EntityData.register(VillagerData.class, "villager", Villager.class, 0, "villager", "normal", "armorer", "butcher", "cartographer", "cleric", "farmer", "fisherman", "fletcher", "leatherworker", "librarian", "mason", "nitwit", "shepherd", "toolsmith", "weaponsmith"); - professions = Arrays.asList(Profession.values()); + // TODO obtain from the registry in the future + // This is not currently done as the ordering of the professions is important + // There is no ordering guarantee from the registry + professions = Arrays.asList(Profession.NONE, Profession.ARMORER, Profession.BUTCHER, Profession.CARTOGRAPHER, + Profession.CLERIC, Profession.FARMER, Profession.FISHERMAN, Profession.FLETCHER, Profession.LEATHERWORKER, + Profession.LIBRARIAN, Profession.MASON, Profession.NITWIT, Profession.SHEPHERD, Profession.TOOLSMITH, + Profession.WEAPONSMITH); } else { // Post 1.10: Not all professions go for villagers EntityData.register(VillagerData.class, "villager", Villager.class, 0, "normal", "villager", "farmer", "librarian", "priest", "blacksmith", "butcher", "nitwit"); // Normal is for zombie villagers, but needs to be here, since someone thought changing first element in enum was good idea :( - professions = new ArrayList<>(); - for (Profession prof : Profession.values()) { - // We're better off doing stringfying the constants since these don't exist in 1.14 - if (!prof.toString().equals("NORMAL") && !prof.toString().equals("HUSK")) - professions.add(prof); + try { + for (Profession prof : (Profession[]) MethodHandles.lookup().findStatic(Profession.class, "values", MethodType.methodType(Profession[].class)).invoke()) { + // We're better off doing stringfying the constants since these don't exist in 1.14 + // Using String#valueOf to prevent IncompatibleClassChangeError due to Enum->Interface change + String profString = String.valueOf(prof); + if (!profString.equals("NORMAL") && !profString.equals("HUSK")) + professions.add(prof); + } + } catch (Throwable e) { + throw new RuntimeException("Failed to load legacy villager profession support", e); } } } @@ -116,7 +131,7 @@ public Class getType() { @Override protected int hashCode_i() { - return profession != null ? profession.hashCode() : 0; + return Objects.hashCode(profession); } @Override @@ -133,7 +148,8 @@ protected boolean deserialize(final String s) { if (s.isEmpty()) return true; try { - profession = Profession.valueOf(s); + //noinspection unchecked, rawtypes - prevent IncompatibleClassChangeError due to Enum->Interface change + profession = (Profession) Enum.valueOf((Class) Profession.class, s); return true; } catch (final IllegalArgumentException e) { return false; @@ -143,7 +159,7 @@ protected boolean deserialize(final String s) { @Override public boolean isSupertypeOf(final EntityData e) { if (e instanceof VillagerData) - return profession == null || ((VillagerData) e).profession == profession; + return profession == null || Objects.equals(((VillagerData) e).profession, profession); return false; } diff --git a/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java b/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java index 3ac61bced2d..24b4080ec1f 100644 --- a/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java +++ b/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java @@ -28,34 +28,53 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + public class ZombieVillagerData extends EntityData { - private final static boolean PROFESSION_UPDATE = Skript.isRunningMinecraft(1, 14); - private final static Villager.Profession[] professions = Villager.Profession.values(); + private static final boolean PROFESSION_UPDATE = Skript.isRunningMinecraft(1, 14); + private static final List professions; static { - if (PROFESSION_UPDATE) + if (PROFESSION_UPDATE) { EntityData.register(ZombieVillagerData.class, "zombie villager", ZombieVillager.class, 0, "zombie villager", "zombie armorer", "zombie butcher", "zombie cartographer", "zombie cleric", "zombie farmer", "zombie fisherman", "zombie fletcher", "zombie leatherworker", "zombie librarian", "zombie mason", "zombie nitwit", "zombie shepherd", "zombie toolsmith", "zombie weaponsmith"); - else + professions = Arrays.asList(Profession.NONE, Profession.ARMORER, Profession.BUTCHER, Profession.CARTOGRAPHER, + Profession.CLERIC, Profession.FARMER, Profession.FISHERMAN, Profession.FLETCHER, Profession.LEATHERWORKER, + Profession.LIBRARIAN, Profession.MASON, Profession.NITWIT, Profession.SHEPHERD, Profession.TOOLSMITH, + Profession.WEAPONSMITH); + } else { EntityData.register(ZombieVillagerData.class, "zombie villager", ZombieVillager.class, 0, "zombie villager", "zombie farmer", "zombie librarian", "zombie priest", "zombie blacksmith", "zombie butcher", "zombie nitwit"); + try { + professions = Arrays.asList((Profession[]) MethodHandles.lookup().findStatic(Profession.class, "values", MethodType.methodType(Profession[].class)).invoke()); + } catch (Throwable e) { + throw new RuntimeException("Failed to load legacy villager profession support", e); + } + } } - - private Villager.Profession profession = PROFESSION_UPDATE ? Profession.NONE : Profession.valueOf("NORMAL"); + + // prevent IncompatibleClassChangeError due to Enum->Interface change + @SuppressWarnings({"unchecked", "rawtypes"}) + private Villager.Profession profession = PROFESSION_UPDATE ? Profession.NONE + : (Profession) Enum.valueOf((Class) Profession.class, "NORMAL"); public ZombieVillagerData() {} public ZombieVillagerData(Profession prof) { profession = prof; - super.matchedPattern = prof.ordinal(); + super.matchedPattern = professions.indexOf(prof); } @SuppressWarnings("null") @Override protected boolean init(final Literal[] exprs, final int matchedPattern, final ParseResult parseResult) { - profession = professions[matchedPattern]; + profession = professions.get(matchedPattern); return true; } @@ -73,8 +92,8 @@ protected boolean init(final @Nullable Class c, final @Override protected boolean deserialize(final String s) { try { - profession = professions[Integer.parseInt(s)]; - } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + profession = professions.get(Integer.parseInt(s)); + } catch (NumberFormatException | IndexOutOfBoundsException e) { throw new SkriptAPIException("Cannot parse zombie villager type " + s); } @@ -106,13 +125,13 @@ protected boolean equals_i(final EntityData obj) { @Override protected int hashCode_i() { - return profession.hashCode(); + return Objects.hashCode(profession); } @Override public boolean isSupertypeOf(final EntityData e) { if (e instanceof ZombieVillagerData) - return ((ZombieVillagerData) e).profession.equals(profession); + return Objects.equals(((ZombieVillagerData) e).profession, profession); return false; } diff --git a/src/main/java/ch/njol/skript/events/EvtHealing.java b/src/main/java/ch/njol/skript/events/EvtHealing.java new file mode 100644 index 00000000000..c1a1cee264a --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtHealing.java @@ -0,0 +1,100 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; + +public class EvtHealing extends SkriptEvent { + + static { + Skript.registerEvent("Heal", EvtHealing.class, EntityRegainHealthEvent.class, "heal[ing] [of %-entitydatas%] [(from|due to|by) %-healreasons%]", "%entitydatas% heal[ing] [(from|due to|by) %-healreasons%]") + .description("Called when an entity is healed, e.g. by eating (players), being fed (pets), or by the effect of a potion of healing (overworld mobs) or harm (nether mobs).") + .examples( + "on heal:", + "on player healing from a regeneration potion:", + "on healing of a zombie, cow or a wither:", + "\theal reason is healing potion", + "\tcancel event" + ) + .since("1.0, 2.9.0 (by reason)"); + } + + @Nullable + private Literal> entityDatas; + + @Nullable + private Literal healReasons; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Literal[] args, int matchedPattern, ParseResult parser) { + entityDatas = (Literal>) args[0]; + healReasons = (Literal) args[1]; + return true; + } + + @Override + public boolean check(Event event) { + if (!(event instanceof EntityRegainHealthEvent)) + return false; + EntityRegainHealthEvent healthEvent = (EntityRegainHealthEvent) event; + if (entityDatas != null) { + Entity compare = healthEvent.getEntity(); + boolean result = false; + for (EntityData entityData : entityDatas.getAll()) { + if (entityData.isInstance(compare)) { + result = true; + break; + } + } + if (!result) + return false; + } + if (healReasons != null) { + RegainReason compare = healthEvent.getRegainReason(); + boolean result = false; + for (RegainReason healReason : healReasons.getAll()) { + if (healReason == compare) { + result = true; + break; + } + } + if (!result) + return false; + } + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "heal" + (entityDatas != null ? " of " + entityDatas.toString(event, debug) : "") + + (healReasons != null ? " by " + healReasons.toString(event, debug) : ""); + } + +} diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 01aa9436072..0eec1d9aa1c 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -132,7 +132,6 @@ public boolean check(Event event) { } @Override - @Nullable @SuppressWarnings("unchecked") public Class [] getEventClasses() { if (isPlayer) { @@ -140,7 +139,7 @@ public boolean check(Event event) { } else if (HAS_ENTITY_MOVE) { return new Class[] {EntityMoveEvent.class}; } - return null; + throw new IllegalStateException("This event has not yet initialized!"); } @Override diff --git a/src/main/java/ch/njol/skript/events/EvtMoveOn.java b/src/main/java/ch/njol/skript/events/EvtMoveOn.java index 52b552b0040..c5677e5783c 100644 --- a/src/main/java/ch/njol/skript/events/EvtMoveOn.java +++ b/src/main/java/ch/njol/skript/events/EvtMoveOn.java @@ -33,6 +33,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.block.Block; import org.bukkit.event.Event; import org.bukkit.event.Listener; @@ -66,9 +67,6 @@ public class EvtMoveOn extends SkriptEvent { ).since("2.0"); } - // Fence blocks and fence gates - private static final ItemType FENCE_PART = Aliases.javaItemType("fence part"); - private static final Map> ITEM_TYPE_TRIGGERS = new ConcurrentHashMap<>(); private static final AtomicBoolean REGISTERED_EXECUTOR = new AtomicBoolean(); @@ -87,10 +85,10 @@ public class EvtMoveOn extends SkriptEvent { if (triggers == null) return; - int y = getBlockY(to.getY(), id); + int y = getBlockY(to.getY(), block); if (to.getWorld().equals(from.getWorld()) && to.getBlockX() == from.getBlockX() && to.getBlockZ() == from.getBlockZ()) { Block fromOnBlock = getOnBlock(from); - if (fromOnBlock != null && y == getBlockY(from.getY(), fromOnBlock.getType()) && fromOnBlock.getType() == id) + if (fromOnBlock != null && y == getBlockY(from.getY(), fromOnBlock) && fromOnBlock.getType() == id) return; } @@ -114,14 +112,14 @@ private static Block getOnBlock(Location location) { Block block = location.getWorld().getBlockAt(location.getBlockX(), (int) (Math.ceil(location.getY()) - 1), location.getBlockZ()); if (block.getType() == Material.AIR && Math.abs((location.getY() - location.getBlockY()) - 0.5) < Skript.EPSILON) { // Fences block = location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY() - 1, location.getBlockZ()); - if (!FENCE_PART.isOfType(block)) + if (!ItemUtils.isFence(block)) return null; } return block; } - private static int getBlockY(double y, Material id) { - if (FENCE_PART.isOfType(id) && Math.abs((y - Math.floor(y)) - 0.5) < Skript.EPSILON) + private static int getBlockY(double y, Block block) { + if (ItemUtils.isFence(block) && Math.abs((y - Math.floor(y)) - 0.5) < Skript.EPSILON) return (int) Math.floor(y) - 1; return (int) Math.ceil(y) - 1; } diff --git a/src/main/java/ch/njol/skript/events/EvtPressurePlate.java b/src/main/java/ch/njol/skript/events/EvtPressurePlate.java index de320c57f26..4d3f876a3b5 100644 --- a/src/main/java/ch/njol/skript/events/EvtPressurePlate.java +++ b/src/main/java/ch/njol/skript/events/EvtPressurePlate.java @@ -19,6 +19,7 @@ package ch.njol.skript.events; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.block.Block; import org.bukkit.event.Event; import org.bukkit.event.block.Action; @@ -36,6 +37,9 @@ * @author Peter Güttinger */ public class EvtPressurePlate extends SkriptEvent { + + private static final boolean HAS_PRESSURE_PLATE_TAG = Skript.fieldExists(Tag.class, "PRESSURE_PLATES"); + static { // TODO is EntityInteractEvent similar for entities? Skript.registerEvent("Pressure Plate / Trip", EvtPressurePlate.class, PlayerInteractEvent.class, @@ -46,8 +50,6 @@ public class EvtPressurePlate extends SkriptEvent { .since("1.0 (pressure plate), 1.4.4 (tripwire)"); } - private static final ItemType plate = Aliases.javaItemType("pressure plate"); - private boolean tripwire; @Override @@ -55,14 +57,25 @@ public boolean init(final Literal[] args, final int matchedPattern, final Par tripwire = matchedPattern == 1; return true; } - + @Override - public boolean check(final Event e) { - final Block b = ((PlayerInteractEvent) e).getClickedBlock(); - final Material type = b == null ? null : b.getType(); - return type != null && ((PlayerInteractEvent) e).getAction() == Action.PHYSICAL && - (tripwire ? (type == Material.TRIPWIRE || type == Material.TRIPWIRE_HOOK) - : plate.isOfType(type)); + public boolean check(Event event) { + Block clickedBlock = ((PlayerInteractEvent) event).getClickedBlock(); + Material type = clickedBlock == null ? null : clickedBlock.getType(); + if (type == null || ((PlayerInteractEvent) event).getAction() != Action.PHYSICAL ) + return false; + + if (tripwire) + return(type == Material.TRIPWIRE || type == Material.TRIPWIRE_HOOK); + + // TODO: 1.16+, remove check in 2.10 + if (HAS_PRESSURE_PLATE_TAG) + return Tag.PRESSURE_PLATES.isTagged(type); + + return Tag.WOODEN_PRESSURE_PLATES.isTagged(type) + || type == Material.HEAVY_WEIGHTED_PRESSURE_PLATE + || type == Material.LIGHT_WEIGHTED_PRESSURE_PLATE + || type == Material.STONE_PRESSURE_PLATE; } @Override diff --git a/src/main/java/ch/njol/skript/events/EvtTeleport.java b/src/main/java/ch/njol/skript/events/EvtTeleport.java new file mode 100644 index 00000000000..6ff92b6d642 --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtTeleport.java @@ -0,0 +1,93 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.entity.EntityType; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.Nullable; + +public class EvtTeleport extends SkriptEvent { + + static { + Skript.registerEvent("Teleport", EvtTeleport.class, CollectionUtils.array(EntityTeleportEvent.class, PlayerTeleportEvent.class), "[%entitytypes%] teleport[ing]") + .description( + "This event can be used to listen to teleports from non-players or player entities respectively.", + "When teleporting entities, the event may also be called due to a result of natural causes, such as an enderman or shulker teleporting, or wolves teleporting to players.", + "When teleporting players, the event can be called by teleporting through a nether/end portal, or by other means (e.g. plugins).") + .examples( + "on teleport:", + "on player teleport:", + "on creeper teleport:" + ) + .since("1.0, 2.9.0 (entity teleport)"); + } + + @Nullable + private Literal entitiesLiteral; + private EntityType @Nullable [] entities; + + @Override + public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult) { + if (args[0] != null) { + entitiesLiteral = ((Literal) args[0]); // evaluate only once + entities = entitiesLiteral.getAll(); + } + return true; + } + + + @Override + public boolean check(Event event) { + if (event instanceof EntityTeleportEvent) { + Entity entity = ((EntityTeleportEvent) event).getEntity(); + return checkEntity(entity); + } else if (event instanceof PlayerTeleportEvent) { + Entity entity = ((PlayerTeleportEvent) event).getPlayer(); + return checkEntity(entity); + } else { + return false; + } + } + + private boolean checkEntity(Entity entity) { + if (entities != null) { + for (EntityType entType : entities) { + if (entType.isInstance(entity)) + return true; + } + return false; + } + return true; + } + + public String toString(@Nullable Event event, boolean debug) { + if (entitiesLiteral != null) + return "on " + entitiesLiteral.toString(event, debug) + " teleport"; + return "on teleport"; + } + +} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index c470d68a07d..947148aaf30 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -57,7 +57,6 @@ import org.bukkit.event.entity.EntityMountEvent; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; -import org.bukkit.event.entity.EntityRegainHealthEvent; import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; import org.bukkit.event.entity.EntityToggleGlideEvent; @@ -209,10 +208,6 @@ public class SimpleEvents { .description("Called when an entity enters a nether portal or an end portal. Please note that this event will be fired many times for a nether portal.") .examples("on portal enter:") .since("1.0"); - Skript.registerEvent("Heal", SimpleEvent.class, EntityRegainHealthEvent.class, "heal[ing]") - .description("Called when an entity is healed, e.g. by eating (players), being fed (pets), or by the effect of a potion of healing (overworld mobs) or harm (nether mobs).") - .examples("on heal:") - .since("1.0"); Skript.registerEvent("Tame", SimpleEvent.class, EntityTameEvent.class, "[entity] tam(e|ing)") .description("Called when a player tames a wolf or ocelot. Can be cancelled to prevent the entity from being tamed.") .examples("on tame:") @@ -325,10 +320,6 @@ public class SimpleEvents { .description("Called when a player respawns. You should prefer this event over the death event as the player is technically alive when this event is called.") .examples("on respawn:") .since("1.0"); - Skript.registerEvent("Teleport", SimpleEvent.class, PlayerTeleportEvent.class, "[player] teleport[ing]") - .description("Called whenever a player is teleported, either by a nether/end portal or other means (e.g. by plugins).") - .examples("on teleport:") - .since("1.0"); Skript.registerEvent("Sneak Toggle", SimpleEvent.class, PlayerToggleSneakEvent.class, "[player] toggl(e|ing) sneak", "[player] sneak toggl(e|ing)") .description("Called when a player starts or stops sneaking. Use is sneaking to get whether the player was sneaking before the event was called.") .examples("# make players that stop sneaking jump", @@ -776,7 +767,7 @@ public class SimpleEvents { "on bell ring:", "\tsend \"Ding-dong!\" to all players in radius 10 of event-block" ) - .since("INSERT VERSION") + .since("2.9.0") .requiredPlugins("Spigot 1.19.4+ or Paper 1.16.5+ (no event-direction)"); } } @@ -794,7 +785,7 @@ public class SimpleEvents { "on bell resonate:", "\tsend \"Raiders are nearby!\" to all players in radius 32 around event-block" ) - .since("INSERT VERSION") + .since("2.9.0") .requiredPlugins("Spigot 1.19.4+"); } @@ -810,7 +801,7 @@ public class SimpleEvents { "\tif player has permission \"safeFrom.enderman\":", "\t\tcancel event" ) - .since("INSERT VERSION") + .since("2.9.0") .requiredPlugins("Paper"); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java index 3763d242dc1..0d98c551925 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java @@ -23,6 +23,7 @@ import ch.njol.skript.classes.Changer.ChangeMode; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; import org.bukkit.block.Furnace; import org.bukkit.event.Event; import org.bukkit.event.inventory.FurnaceBurnEvent; @@ -65,8 +66,6 @@ public class ExprBurnCookTime extends PropertyExpression { "[the] (burn|1:cook)[ing] time of %blocks%", "%blocks%'[s] (burn|1:cook)[ing] time"); } - - static final ItemType anyFurnace = Aliases.javaItemType("any furnace"); private boolean cookTime; private boolean isEvent; @@ -92,10 +91,11 @@ protected Timespan[] get(Event event, Block[] source) { return CollectionUtils.array(Timespan.fromTicks(((FurnaceBurnEvent) event).getBurnTime())); } else { return Arrays.stream(source) - .filter(anyFurnace::isOfType) - .map(furnace -> { - Furnace state = (Furnace) furnace.getState(); - return Timespan.fromTicks(cookTime ? state.getCookTime() : state.getBurnTime()); + .map(Block::getState) + .filter(blockState -> blockState instanceof Furnace) + .map(state -> { + Furnace furnace = (Furnace) state; + return Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime()); }) .toArray(Timespan[]::new); } @@ -141,7 +141,8 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { } for (Block block : getExpr().getArray(event)) { - if (!anyFurnace.isOfType(block)) + BlockState state = block.getState(); + if (!(state instanceof Furnace)) continue; Furnace furnace = (Furnace) block.getState(); long time = value.apply(Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getTicks(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java b/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java index 94305f638de..794f162b107 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCharacterFromCodepoint.java @@ -42,7 +42,7 @@ "\t\tadd character from codepoint loop-value to {_chars::*}", "\treturn {_chars::*}", }) -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprCharacterFromCodepoint extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java index 0ba4548c184..a01ac6b9dcc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java @@ -52,7 +52,7 @@ "set slot 4 of {_inventory} to a diamond named \"example\"", "open {_inventory} to player", "", - "open chest inventory named \"<##00ff00>hex coloured title!\" with 6 rows to player", + "open chest inventory named \"<#00ff00>hex coloured title!\" with 6 rows to player", }) @RequiredPlugins("Paper 1.16+ (chat format)") @Since("2.2-dev34, 2.8.0 (chat format)") diff --git a/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java b/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java index 17d4ae50a4e..e72f7adf5b7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCodepoint.java @@ -46,7 +46,7 @@ "\t\tset {_previous-codepoint} to {_codepoint}", "\treturn true" }) -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprCodepoint extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index e946cdb02ec..bf8dd737b00 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -18,25 +18,22 @@ */ package ch.njol.skript.expressions; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; -import org.bukkit.Material; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; @Name("Damage Value/Durability") @Description("The damage value/durability of an item.") @@ -51,7 +48,7 @@ public class ExprDurability extends SimplePropertyExpression { private boolean durability; static { - register(ExprDurability.class, Integer.class, "(damage[s] [value[s]]|1:durabilit(y|ies))", "itemtypes/slots"); + register(ExprDurability.class, Integer.class, "(damage[s] [value[s]]|1:durabilit(y|ies))", "itemtypes/itemstacks/slots"); } @Override @@ -63,11 +60,11 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Integer convert(Object object) { - ItemType itemType = asItemType(object); - if (itemType == null) + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) return null; - ItemMeta meta = itemType.getItemMeta(); - return meta instanceof Damageable ? convertToDamage(itemType.getMaterial(), ((Damageable) meta).getDamage()) : 0; + int damage = ItemUtils.getDamage(itemStack); + return convertToDamage(itemStack, damage); } @Override @@ -90,40 +87,38 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (mode == ChangeMode.REMOVE) change = -change; for (Object object : getExpr().getArray(event)) { - ItemType itemType = asItemType(object); - if (itemType == null) - continue; - - ItemMeta meta = itemType.getItemMeta(); - if (!(meta instanceof Damageable)) + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) continue; - Damageable damageable = (Damageable) meta; - Material material = itemType.getMaterial(); + int newAmount; switch (mode) { case ADD: case REMOVE: - int current = convertToDamage(material, damageable.getDamage()); - damageable.setDamage(convertToDamage(material, current + change)); + int current = convertToDamage(itemStack, ItemUtils.getDamage(itemStack)); + newAmount = current + change; break; case SET: - damageable.setDamage(convertToDamage(material, change)); + newAmount = change; break; - case DELETE: - case RESET: - damageable.setDamage(0); + default: + newAmount = 0; } - itemType.setItemMeta(meta); + ItemUtils.setDamage(itemStack, convertToDamage(itemStack, newAmount)); if (object instanceof Slot) - ((Slot) object).setItem(itemType.getRandom()); + ((Slot) object).setItem(itemStack); + else if (object instanceof ItemType) + ((ItemType) object).setItemMeta(itemStack.getItemMeta()); } } - private int convertToDamage(Material material, int value) { + private int convertToDamage(ItemStack itemStack, int value) { if (!durability) return value; - int maxDurability = material.getMaxDurability(); + + int maxDurability = ItemUtils.getMaxDamage(itemStack); + if (maxDurability == 0) return 0; return maxDurability - value; @@ -139,14 +134,4 @@ public String getPropertyName() { return durability ? "durability" : "damage"; } - @Nullable - private static ItemType asItemType(Object object) { - if (object instanceof ItemType) - return (ItemType) object; - ItemStack itemStack = ((Slot) object).getItem(); - if (itemStack == null) - return null; - return new ItemType(itemStack); - } - } diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilter.java b/src/main/java/ch/njol/skript/expressions/ExprFilter.java index 239b720e418..a9747bbc3fc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFilter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFilter.java @@ -27,83 +27,97 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.InputSource; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import ch.njol.util.coll.iterator.ArrayIterator; import com.google.common.collect.Iterators; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import java.lang.reflect.Array; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; -import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.regex.Pattern; +import java.util.stream.StreamSupport; @Name("Filter") -@Description("Filters a list based on a condition. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', " + - "only \"something\" would be broadcast as it is the only string that matched the condition.") +@Description({ + "Filters a list based on a condition. ", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', ", + "only \"something\" would be broadcast as it is the only string that matched the condition." +}) @Examples("send \"congrats on being staff!\" to all players where [player input has permission \"staff\"]") @Since("2.2-dev36") @SuppressWarnings({"null", "unchecked"}) -public class ExprFilter extends SimpleExpression { - - @Nullable - private static ExprFilter parsing; +public class ExprFilter extends SimpleExpression implements InputSource { static { Skript.registerExpression(ExprFilter.class, Object.class, ExpressionType.COMBINED, "%objects% (where|that match) \\[<.+>\\]"); + if (!ParserInstance.isRegistered(InputData.class)) + ParserInstance.registerData(InputData.class, InputData::new); } - private Object current; - private List> children = new ArrayList<>(); - private Condition condition; - private String rawCond; - private Expression objects; + private Condition filterCondition; + private String unparsedCondition; + private Expression unfilteredObjects; + private Set> dependentInputs = new HashSet<>(); @Nullable - public static ExprFilter getParsing() { - return parsing; - } + private Object currentFilterValue; + @Nullable + private String currentFilterIndex; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - try { - parsing = this; - objects = LiteralUtils.defendExpression(exprs[0]); - if (objects.isSingle()) - return false; - rawCond = parseResult.regexes.get(0).group(); - condition = Condition.parse(rawCond, "Can't understand this condition: " + rawCond); - } finally { - parsing = null; - } - return condition != null && LiteralUtils.canInitSafely(objects); + unfilteredObjects = LiteralUtils.defendExpression(exprs[0]); + if (unfilteredObjects.isSingle() || !LiteralUtils.canInitSafely(unfilteredObjects)) + return false; + unparsedCondition = parseResult.regexes.get(0).group(); + InputData inputData = getParser().getData(InputData.class); + InputSource originalSource = inputData.getSource(); + inputData.setSource(this); + filterCondition = Condition.parse(unparsedCondition, "Can't understand this condition: " + unparsedCondition); + inputData.setSource(originalSource); + return filterCondition != null; } - @NonNull + @NotNull @Override public Iterator iterator(Event event) { - Iterator objIterator = this.objects.iterator(event); - if (objIterator == null) + if (unfilteredObjects instanceof Variable) { + Iterator> variableIterator = ((Variable) unfilteredObjects).variablesIterator(event); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(variableIterator, Spliterator.ORDERED), false) + .filter(pair -> { + currentFilterValue = pair.getValue(); + currentFilterIndex = pair.getKey(); + return filterCondition.check(event); + }) + .map(Pair::getValue) + .iterator(); + } + + // clear current index just to be safe + currentFilterIndex = null; + + Iterator unfilteredObjectIterator = unfilteredObjects.iterator(event); + if (unfilteredObjectIterator == null) return Collections.emptyIterator(); - try { - return Iterators.filter(objIterator, object -> { - current = object; - return condition.check(event); - }); - } finally { - current = null; - } + return Iterators.filter(unfilteredObjectIterator, candidateObject -> { + currentFilterValue = candidateObject; + return filterCondition.check(event); + }); } @Override @@ -115,148 +129,64 @@ protected Object[] get(Event event) { } } - public Object getCurrent() { - return current; - } - - private void addChild(ExprInput child) { - children.add(child); - } - - private void removeChild(ExprInput child) { - children.remove(child); + @Override + public boolean isSingle() { + return false; } @Override public Class getReturnType() { - return objects.getReturnType(); + return unfilteredObjects.getReturnType(); } - @Override - public boolean isSingle() { - return objects.isSingle(); - } @Override public String toString(Event event, boolean debug) { - return String.format("%s where [%s]", objects.toString(event, debug), rawCond); + return unfilteredObjects.toString(event, debug) + " that match [" + unparsedCondition + "]"; } - @Override - public boolean isLoopOf(String s) { - for (ExprInput child : children) { // if they used player input, let's assume loop-player is valid - if (child.getClassInfo() == null || child.getClassInfo().getUserInputPatterns() == null) - continue; + private boolean matchesAnySpecifiedTypes(String candidateString) { + for (ExprInput dependentInput : dependentInputs) { + ClassInfo specifiedType = dependentInput.getSpecifiedType(); + if (specifiedType == null) + return false; + Pattern[] specifiedTypePatterns = specifiedType.getUserInputPatterns(); + if (specifiedTypePatterns == null) + return false; - for (Pattern pattern : child.getClassInfo().getUserInputPatterns()) { - if (pattern.matcher(s).matches()) + for (Pattern typePattern : specifiedTypePatterns) { + if (typePattern.matcher(candidateString).matches()) { return true; + } } } - return objects.isLoopOf(s); // nothing matched, so we'll rely on the object expression's logic + return false; } - @Name("Filter Input") - @Description("Represents the input in a filter expression. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]" + - "the condition would be checked twice, using \"something\" and \"something else\" as the inputs.") - @Examples("send \"congrats on being staff!\" to all players where [input has permission \"staff\"]") - @Since("2.2-dev36") - public static class ExprInput extends SimpleExpression { - - static { - Skript.registerExpression(ExprInput.class, Object.class, ExpressionType.COMBINED, - "input", - "%*classinfo% input" - ); - } - - @Nullable - private final ExprInput source; - private final Class[] types; - private final Class superType; - @SuppressWarnings("NotNullFieldNotInitialized") - private ExprFilter parent; - @Nullable - private ClassInfo inputType; - - public ExprInput() { - this(null, (Class) Object.class); - } - - public ExprInput(@Nullable ExprInput source, Class... types) { - this.source = source; - if (source != null) { - this.parent = source.parent; - this.inputType = source.inputType; - parent.removeChild(source); - parent.addChild(this); - } - - this.types = types; - this.superType = (Class) Utils.getSuperType(types); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - parent = ExprFilter.getParsing(); - - if (parent == null) - return false; - - parent.addChild(this); - inputType = matchedPattern == 0 ? null : ((Literal>) exprs[0]).getSingle(); - return true; - } - - @Override - protected T[] get(Event event) { - Object current = parent.getCurrent(); - if (inputType != null && !inputType.getC().isInstance(current)) { - return null; - } - - try { - return Converters.convert(new Object[]{current}, types, superType); - } catch (ClassCastException e1) { - return (T[]) Array.newInstance(superType, 0); - } - } - - public void setParent(ExprFilter parent) { - this.parent = parent; - } - - @Override - public Expression getConvertedExpression(Class... to) { - return new ExprInput<>(this, to); - } - @Override - public Expression getSource() { - return source == null ? this : source; - } - - @Override - public Class getReturnType() { - return superType; - } + @Override + public boolean isLoopOf(String candidateString) { + return unfilteredObjects.isLoopOf(candidateString) || matchesAnySpecifiedTypes(candidateString); + } - @Nullable - private ClassInfo getClassInfo() { - return inputType; - } + public Set> getDependentInputs() { + return dependentInputs; + } - @Override - public boolean isSingle() { - return true; - } + @Nullable + public Object getCurrentValue() { + return currentFilterValue; + } - @Override - public String toString(Event event, boolean debug) { - return inputType == null ? "input" : inputType.getCodeName() + " input"; - } + @Override + public boolean hasIndices() { + return unfilteredObjects instanceof Variable; + } + @Override + @UnknownNullability + public String getCurrentIndex() { + return currentFilterIndex; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java index 9e74f6aa68e..ae905e96fd7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java @@ -25,6 +25,7 @@ import ch.njol.skript.Skript; 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.Examples; @@ -32,49 +33,49 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser; +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("Heal Amount") -@Description("The amount of health healed in a healing event.") -@Examples({"increase heal amount by 2", - "remove 0.5 from heal amount"}) -@Since("2.5.1") +@Description("The amount of health healed in a heal event.") +@Examples({ + "on player healing:", + "\tincrease the heal amount by 2", + "\tremove 0.5 from the healing amount" +}) @Events("heal") -public class ExprHealAmount extends SimpleExpression { - +@Since("2.5.1") +public class ExprHealAmount extends SimpleExpression { + static { - Skript.registerExpression(ExprHealAmount.class, Number.class, ExpressionType.SIMPLE, "[the] heal amount"); + Skript.registerExpression(ExprHealAmount.class, Double.class, ExpressionType.SIMPLE, "[the] heal[ing] amount"); } - - @SuppressWarnings("null") + private Kleenean delay; - + @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (!getParser().isCurrentEvent(EntityRegainHealthEvent.class)) { - Skript.error("The expression 'heal amount' may only be used in a healing event", ErrorQuality.SEMANTIC_ERROR); + Skript.error("The expression 'heal amount' may only be used in a healing event"); return false; } delay = isDelayed; return true; } - + @Nullable @Override - protected Number[] get(Event e) { - if (!(e instanceof EntityRegainHealthEvent)) + protected Double[] get(Event event) { + if (!(event instanceof EntityRegainHealthEvent)) return null; - - return new Number[]{((EntityRegainHealthEvent) e).getAmount()}; + return new Double[]{((EntityRegainHealthEvent) event).getAmount()}; } - + @Nullable @Override - public Class[] acceptChange(Changer.ChangeMode mode) { + public Class[] acceptChange(ChangeMode mode) { if (delay != Kleenean.FALSE) { Skript.error("The heal amount cannot be changed after the event has already passed"); return null; @@ -83,42 +84,48 @@ public Class[] acceptChange(Changer.ChangeMode mode) { return null; return CollectionUtils.array(Number.class); } - + @Override - public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { - if (!(e instanceof EntityRegainHealthEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof EntityRegainHealthEvent)) return; + EntityRegainHealthEvent healthEvent = (EntityRegainHealthEvent) event; double value = delta == null ? 0 : ((Number) delta[0]).doubleValue(); switch (mode) { case SET: case DELETE: - ((EntityRegainHealthEvent) e).setAmount(value); + healthEvent.setAmount(value); break; case ADD: - ((EntityRegainHealthEvent) e).setAmount(((EntityRegainHealthEvent) e).getAmount() + value); + healthEvent.setAmount(healthEvent.getAmount() + value); break; case REMOVE: - ((EntityRegainHealthEvent) e).setAmount(((EntityRegainHealthEvent) e).getAmount() - value); + healthEvent.setAmount(healthEvent.getAmount() - value); break; default: break; } } - + + @Override + public boolean setTime(int time) { + return super.setTime(time, EntityRegainHealthEvent.class); + } + @Override public boolean isSingle() { return true; } - + @Override - public Class getReturnType() { - return Number.class; + public Class getReturnType() { + return Double.class; } - + @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "heal amount"; } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java index 12dc0270976..330ec4ed4d7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java @@ -18,67 +18,40 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityRegainHealthEvent; -import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; 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.skript.expressions.base.EventValueExpression; +import ch.njol.skript.registrations.EventValues; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; @Name("Heal Reason") -@Description("The heal reason of a heal event. Please click on the link for more information.") -@Examples({"on heal:", - "\tif heal reason = satiated:", - "\t\tsend \"You ate enough food and gained health back!\" to player"}) +@Description("The heal reason of a heal event.") +@Examples({ + "on heal:", + "\theal reason is satiated", + "\tsend \"You ate enough food and gained full health back!\"" +}) +@Events("heal") @Since("2.5") -public class ExprHealReason extends SimpleExpression { - +public class ExprHealReason extends EventValueExpression { + static { - Skript.registerExpression(ExprHealReason.class, RegainReason.class, ExpressionType.SIMPLE, "(regen|health regain|heal) (reason|cause)"); + register(ExprHealReason.class, RegainReason.class, "(regen|health regain|heal[ing]) (reason|cause)"); } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (!getParser().isCurrentEvent(EntityRegainHealthEvent.class)) { - Skript.error("Heal reason can only be used in an on heal event", ErrorQuality.SEMANTIC_ERROR); - return false; - } - return true; - } - - @Nullable - @Override - protected RegainReason[] get(Event e) { - if (!(e instanceof EntityRegainHealthEvent)) - return null; - return new RegainReason[]{((EntityRegainHealthEvent) e).getRegainReason()}; + public ExprHealReason() { + super(RegainReason.class); } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public Class getReturnType() { - return RegainReason.class; - } - + @Override - public String toString(@Nullable Event e, boolean debug) { - return "heal reason"; + public boolean setTime(int time) { + if (time == EventValues.TIME_FUTURE) + return false; + return super.setTime(time); } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java b/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java deleted file mode 100644 index 8c48e9121ab..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.ExpressionType; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.eclipse.jdt.annotation.Nullable; - -@Name("Highest Solid Block") -@Description("Returns the highest solid block at the x and z coordinates of the world of a given location.") -@Examples("highest block at location of arg-player") -@Since("2.2-dev34") -public class ExprHighestSolidBlock extends SimplePropertyExpression { - - static { - Skript.registerExpression(ExprHighestSolidBlock.class, Block.class, ExpressionType.PROPERTY, "highest [(solid|non-air)] block at %locations%"); - } - - @Override - protected String getPropertyName() { - return "highest [(solid|non-air)] block"; - } - - @Nullable - @Override - public Block convert(Location location) { - return location.getWorld().getHighestBlockAt(location); - } - - @Override - public Class getReturnType() { - return Block.class; - } -} \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/expressions/ExprIndices.java b/src/main/java/ch/njol/skript/expressions/ExprIndices.java index c09bfe81964..87dffe51ae5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIndices.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIndices.java @@ -30,6 +30,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.Pair; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -101,8 +102,14 @@ protected String[] get(Event e) { if (sort) { int direction = descending ? -1 : 1; return variable.entrySet().stream() + .map((entry) -> new Pair<>( + entry.getKey(), + entry.getValue() instanceof Map + ? ((Map) entry.getValue()).get(null) + : entry.getValue() + )) .sorted((a, b) -> ExprSortedList.compare(a.getValue(), b.getValue()) * direction) - .map(Entry::getKey) + .map(Pair::getKey) .toArray(String[]::new); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprInput.java b/src/main/java/ch/njol/skript/expressions/ExprInput.java new file mode 100644 index 00000000000..c04df548244 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprInput.java @@ -0,0 +1,170 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.InputSource; +import ch.njol.skript.lang.InputSource.InputData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.DefaultClasses; +import ch.njol.skript.util.ClassInfoReference; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +import java.lang.reflect.Array; +import java.util.Set; + +@Name("Input") +@Description({ + "Represents the input in a filter expression or sort effect.", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]", + "the condition would be checked twice, using \"something\" and \"something else\" as the inputs.", + "The 'input index' pattern can be used when acting on a variable to access the index of the input." +}) +@Examples({ + "send \"congrats on being staff!\" to all players where [input has permission \"staff\"]", + "sort {_list::*} based on length of input index" +}) +@Since("2.2-dev36, 2.9.0 (input index)") +public class ExprInput extends SimpleExpression { + + static { + Skript.registerExpression(ExprInput.class, Object.class, ExpressionType.COMBINED, + "input", + "%*classinfo% input", + "input index" + ); + } + + @Nullable + private final ExprInput source; + private final Class[] types; + private final Class superType; + + private InputSource inputSource; + + @Nullable + private ClassInfo specifiedType; + private boolean isIndex = false; + + public ExprInput() { + this(null, (Class) Object.class); + } + + public ExprInput(@Nullable ExprInput source, Class... types) { + this.source = source; + if (source != null) { + isIndex = source.isIndex; + specifiedType = source.specifiedType; + inputSource = source.inputSource; + Set> dependentInputs = inputSource.getDependentInputs(); + dependentInputs.remove(this.source); + dependentInputs.add(this); + } + this.types = types; + this.superType = (Class) Utils.getSuperType(types); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + inputSource = getParser().getData(InputData.class).getSource(); + if (inputSource == null) + return false; + switch (matchedPattern) { + case 1: + ClassInfoReference classInfoReference = ((Literal) ClassInfoReference.wrap((Expression>) exprs[0])).getSingle(); + if (classInfoReference.isPlural().isTrue()) { + Skript.error("An input can only be a single value! Please use a singular type (for example: players input -> player input)."); + return false; + } + specifiedType = classInfoReference.getClassInfo(); + break; + case 2: + if (!inputSource.hasIndices()) { + Skript.error("You cannot use 'input index' on lists without indices!"); + return false; + } + specifiedType = DefaultClasses.STRING; + isIndex = true; + break; + default: + specifiedType = null; + } + return true; + } + + @Override + protected T[] get(Event event) { + Object currentValue = isIndex ? inputSource.getCurrentIndex() : inputSource.getCurrentValue(); + if (currentValue == null || (specifiedType != null && !specifiedType.getC().isInstance(currentValue))) + return (T[]) Array.newInstance(superType, 0); + + try { + return Converters.convert(new Object[]{currentValue}, types, superType); + } catch (ClassCastException exception) { + return (T[]) Array.newInstance(superType, 0); + } + } + + @Override + public Expression getConvertedExpression(Class... to) { + return new ExprInput<>(this, to); + } + + @Override + public Expression getSource() { + return source == null ? this : source; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return superType; + } + + @Nullable + public ClassInfo getSpecifiedType() { + return specifiedType; + } + + + @Override + public String toString(Event event, boolean debug) { + if (isIndex) + return "input index"; + return specifiedType == null ? "input" : specifiedType.getCodeName() + " input"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index 0365ebf177e..cfd7cdeae8f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -1,30 +1,7 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.expressions; -import java.util.regex.Pattern; - -import ch.njol.skript.SkriptConfig; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -35,43 +12,48 @@ 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.jetbrains.annotations.UnknownNullability; + +import java.util.regex.Pattern; -/** - * @author Peter Güttinger - */ @Name("Join & Split") @Description("Joins several texts with a common delimiter (e.g. \", \"), or splits a text into multiple texts at a given delimiter.") @Examples({ - "message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", - "set {_s::*} to the string argument split at \",\"" + "message \"Online players: %join all players' names with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", + "set {_s::*} to the string argument split at \",\"" }) -@Since("2.1, 2.5.2 (regex support), 2.7 (case sensitivity)") +@Since("2.1, 2.5.2 (regex support), 2.7 (case sensitivity), INSERT VERSION (without trailing string)") public class ExprJoinSplit extends SimpleExpression { static { Skript.registerExpression(ExprJoinSplit.class, String.class, ExpressionType.COMBINED, "(concat[enate]|join) %strings% [(with|using|by) [[the] delimiter] %-string%]", - "split %string% (at|using|by) [[the] delimiter] %string% [case:with case sensitivity]", - "%string% split (at|using|by) [[the] delimiter] %string% [case:with case sensitivity]", - "regex split %string% (at|using|by) [[the] delimiter] %string%", - "regex %string% split (at|using|by) [[the] delimiter] %string%"); + "split %string% (at|using|by) [[the] delimiter] %string% [case:with case sensitivity] [trailing:without [the] trailing [empty] (string|text)]", + "%string% split (at|using|by) [[the] delimiter] %string% [case:with case sensitivity] [trailing:without [the] trailing [empty] (string|text)]", + "regex split %string% (at|using|by) [[the] delimiter] %string% [trailing:without [the] trailing [empty] (string|text)]", + "regex %string% split (at|using|by) [[the] delimiter] %string% [trailing:without [the] trailing [empty] (string|text)]"); } private boolean join; private boolean regex; private boolean caseSensitivity; + private boolean removeTrailing; - @SuppressWarnings("null") + @UnknownNullability private Expression strings; - @Nullable + @UnknownNullability private Expression delimiter; @Override - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { join = matchedPattern == 0; regex = matchedPattern >= 3; caseSensitivity = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); + removeTrailing = parseResult.hasTag("trailing"); + strings = (Expression) exprs[0]; delimiter = (Expression) exprs[1]; return true; @@ -82,12 +64,18 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected String[] get(Event event) { String[] strings = this.strings.getArray(event); String delimiter = this.delimiter != null ? this.delimiter.getSingle(event) : ""; + if (strings.length == 0 || delimiter == null) return new String[0]; + if (join) { - return new String[] {StringUtils.join(strings, delimiter)}; + return new String[]{StringUtils.join(strings, delimiter)}; } else { - return strings[0].split(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter), -1); + if (!regex) { + delimiter = (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter); + } + + return strings[0].split(delimiter, removeTrailing ? 0 : -1); } } @@ -104,9 +92,13 @@ public Class getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { if (join) - return "join " + strings.toString(event, debug) + (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); - return (regex ? "regex " : "") + "split " + strings.toString(event, debug) + (delimiter != null ? " at " + delimiter.toString(event, debug) : "") - + (regex ? "" : "(case sensitive: " + caseSensitivity + ")"); + return "join " + strings.toString(event, debug) + + (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); + return (regex ? "regex " : "") + + "split " + strings.toString(event, debug) + + (delimiter != null ? " at " + delimiter.toString(event, debug) : "") + + (regex ? "" : "(case sensitive: " + caseSensitivity + ")") + + (removeTrailing ? " without trailing string" : ""); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLastDamage.java b/src/main/java/ch/njol/skript/expressions/ExprLastDamage.java index 0b62cd60a9d..ede427b156f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLastDamage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLastDamage.java @@ -19,47 +19,34 @@ */ package ch.njol.skript.expressions; -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; @Name("Last Damage") @Description("The last damage that was done to an entity. Note that changing it doesn't deal more/less damage.") @Examples({"set last damage of event-entity to 2"}) @Since("2.5.1") public class ExprLastDamage extends SimplePropertyExpression { - + static { register(ExprLastDamage.class, Number.class, "last damage", "livingentities"); } - - @Nullable - private ExprDamage damageExpr; - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - damageExpr = new ExprDamage(); - return true; - } - + @Nullable @Override @SuppressWarnings("null") public Number convert(LivingEntity livingEntity) { - return damageExpr.get(livingEntity.getLastDamageCause())[0]; + return livingEntity.getLastDamage() / 2; } - + @Nullable @Override public Class[] acceptChange(ChangeMode mode) { @@ -72,35 +59,38 @@ public Class[] acceptChange(ChangeMode mode) { return null; } } - + + @SuppressWarnings("ConstantValue") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (delta != null) { + if (delta != null && delta[0] instanceof Number) { + double damage = ((Number) delta[0]).doubleValue() * 2; + switch (mode) { case SET: for (LivingEntity entity : getExpr().getArray(e)) - entity.setLastDamage((Long) delta[0]); + entity.setLastDamage(damage); break; case REMOVE: - delta[0] = (Long) delta[0] * -1; + damage = damage * -1; case ADD: for (LivingEntity entity : getExpr().getArray(e)) - entity.setLastDamage((Long) delta[0] + entity.getLastDamage()); + entity.setLastDamage(damage + entity.getLastDamage()); break; default: assert false; } } } - + @Override public Class getReturnType() { return Number.class; } - + @Override protected String getPropertyName() { return "last damage"; } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java index 37640b26db7..12fb9b0a782 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java @@ -45,7 +45,7 @@ "", "loop {top-balances::*}:", "\tif loop-iteration <= 10:", - "\t\tbroadcast \"##%loop-iteration% %loop-index% has $%loop-value%\"", + "\t\tbroadcast \"#%loop-iteration% %loop-index% has $%loop-value%\"", }) @Since("2.8.0") public class ExprLoopIteration extends SimpleExpression { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 343cc9461c3..10142cb0268 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -62,7 +62,7 @@ "", "loop {top-balances::*}:", "\tloop-iteration <= 10", - "\tsend \"##%loop-iteration% %loop-index% has $%loop-value%\"", + "\tsend \"#%loop-iteration% %loop-index% has $%loop-value%\"", }) @Since("1.0, 2.8.0 (loop-counter)") public class ExprLoopValue extends SimpleExpression { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java b/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java new file mode 100644 index 00000000000..09dd0912f3d --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java @@ -0,0 +1,123 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +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.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.generator.WorldInfo; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Lowest/Highest Solid Block") +@Description({ + "An expression to obtain the lowest or highest solid (impassable) block at a location.", + "Note that the y-coordinate of the location is not taken into account for this expression." +}) +@Examples({ + "teleport the player to the block above the highest block at the player", + "set the highest solid block at the player's location to the lowest solid block at the player's location" +}) +@Since("2.2-dev34, 2.9.0 (lowest solid block, 'non-air' option removed, additional syntax option)") +public class ExprLowestHighestSolidBlock extends SimplePropertyExpression { + + private static final boolean HAS_MIN_HEIGHT = + Skript.classExists("org.bukkit.generator.WorldInfo") && + Skript.methodExists(WorldInfo.class, "getMinHeight"); + + private static final boolean HAS_BLOCK_IS_SOLID = Skript.methodExists(Block.class, "isSolid"); + + // Before 1.15, getHighestSolidBlock actually returned the block directly ABOVE the highest solid block + private static final boolean RETURNS_FIRST_AIR = !Skript.isRunningMinecraft(1, 15); + + static { + Skript.registerExpression(ExprLowestHighestSolidBlock.class, Block.class, ExpressionType.PROPERTY, + "[the] (highest|:lowest) [solid] block (at|of) %locations%", + "%locations%'[s] (highest|:lowest) [solid] block" + ); + } + + private boolean lowest; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + lowest = parseResult.hasTag("lowest"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + @Nullable + public Block convert(Location location) { + World world = location.getWorld(); + if (world == null) { + return null; + } + + if (!lowest) { + return getHighestBlockAt(world, location); + } + + location = location.clone(); + location.setY(HAS_MIN_HEIGHT ? world.getMinHeight() : 0); + Block block = location.getBlock(); + int maxHeight = world.getMaxHeight(); + while (block.getY() < maxHeight && !isSolid(block)) { // work our way up + block = block.getRelative(BlockFace.UP); + } + // if this block isn't solid, there are no solid blocks at this location + // getHighestBlockAt is apparently NotNull, so let's just mimic that behavior by returning it + return isSolid(block) ? block : getHighestBlockAt(world, block.getLocation()); + } + + private static Block getHighestBlockAt(World world, Location location) { + Block block = world.getHighestBlockAt(location); + if (RETURNS_FIRST_AIR) { + block = block.getRelative(BlockFace.DOWN); + if (!isSolid(block)) { // if the one right below isn't solid let's just preserve the behavior + block.getRelative(BlockFace.UP); + } + } + return block; + } + + private static boolean isSolid(Block block) { + return HAS_BLOCK_IS_SOLID ? block.isSolid() : block.getType().isSolid(); + } + + @Override + public Class getReturnType() { + return Block.class; + } + + @Override + protected String getPropertyName() { + return (lowest ? "lowest" : "highest") + " solid block"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxDurability.java b/src/main/java/ch/njol/skript/expressions/ExprMaxDurability.java index dbd19064482..1287a8647f2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxDurability.java @@ -18,46 +18,109 @@ */ package ch.njol.skript.expressions; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.util.slot.Slot; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; @Name("Max Durability") -@Description("The maximum durability of an item.") -@Examples({"maximum durability of diamond sword", - "if max durability of player's tool is not 0: # Item is damageable"}) -@Since("2.5") -public class ExprMaxDurability extends SimplePropertyExpression { +@Description({"The maximum durability of an item. Changing requires Minecraft 1.20.5+", + "Note: 'delete' will remove the max durability from the item (making it a non-damageable item). Delete requires Paper 1.21+"}) +@Examples({ + "maximum durability of diamond sword", + "if max durability of player's tool is not 0: # Item is damageable", + "set max durability of player's tool to 5000", + "add 5 to max durability of player's tool", + "reset max durability of player's tool", + "delete max durability of player's tool" +}) +@RequiredPlugins("Minecraft 1.20.5+ (custom amount)") +@Since("2.5, 2.9.0 (change)") +public class ExprMaxDurability extends SimplePropertyExpression { static { - register(ExprMaxDurability.class, Long.class, "max[imum] durabilit(y|ies)", "itemstacks/slots"); + register(ExprMaxDurability.class, Integer.class, "max[imum] (durabilit(y|ies)|damage)", "itemtypes/itemstacks/slots"); } - + @Override @Nullable - public Long convert(Object o) { - if (o instanceof Slot) { - final ItemStack i = ((Slot) o).getItem(); - return i == null ? null : (long) i.getType().getMaxDurability(); - } else { - return (long) ((ItemStack) o).getType().getMaxDurability(); + public Integer convert(Object object) { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + return null; + return ItemUtils.getMaxDamage(itemStack); + } + + @Override + public @Nullable Class[] acceptChange(ChangeMode mode) { + if (ItemUtils.HAS_MAX_DAMAGE) { + switch (mode) { + case SET: + case ADD: + case REMOVE: + case RESET: + return CollectionUtils.array(Number.class); + case DELETE: + if (ItemUtils.HAS_RESET) + return CollectionUtils.array(); + } + } + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int change = delta == null ? 0 : ((Number) delta[0]).intValue(); + if (mode == ChangeMode.REMOVE) + change = -change; + + for (Object object : getExpr().getArray(event)) { + ItemStack itemStack = ItemUtils.asItemStack(object); + if (itemStack == null) + continue; + + int newValue; + switch (mode) { + case ADD: + case REMOVE: + newValue = ItemUtils.getMaxDamage(itemStack) + change; + break; + case SET: + newValue = change; + break; + case DELETE: + newValue = 0; + break; + default: + newValue = itemStack.getType().getMaxDurability(); + } + + ItemUtils.setMaxDamage(itemStack, newValue); + if (object instanceof Slot) + ((Slot) object).setItem(itemStack); + if (object instanceof ItemType) + ((ItemType) object).setItemMeta(itemStack.getItemMeta()); } } - + @Override - public Class getReturnType() { - return Long.class; + public Class getReturnType() { + return Integer.class; } @Override protected String getPropertyName() { return "max durability"; } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxStack.java b/src/main/java/ch/njol/skript/expressions/ExprMaxStack.java index 71abeb61b7d..77b65fa5191 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxStack.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxStack.java @@ -24,6 +24,8 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; /** * @author joeuguce99 @@ -40,8 +42,11 @@ public class ExprMaxStack extends SimplePropertyExpression { @SuppressWarnings("null") @Override - public Long convert(final ItemType i) { - return (long) i.getRandom().getMaxStackSize(); + public Long convert(ItemType itemType) { + Object random = itemType.getRandomStackOrMaterial(); + if (random instanceof Material) + return (long) ((Material) random).getMaxStackSize(); + return (long) ((ItemStack) random).getMaxStackSize(); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprMessage.java b/src/main/java/ch/njol/skript/expressions/ExprMessage.java index a3b54f43c4c..d9342ff1d20 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMessage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMessage.java @@ -48,23 +48,27 @@ @SuppressWarnings("deprecation") @Name("Message") @Description("The (chat) message of a chat event, the join message of a join event, the quit message of a quit event, or the death message on a death event. This expression is mostly useful for being changed.") -@Examples({"on chat:", - " player has permission \"admin\"", - " set message to \"&c%message%\"", +@Examples({ + "on chat:", + "\tplayer has permission \"admin\"", + "\tset message to \"&c%message%\"", "", "on first join:", - " set join message to \"Welcome %player% to our awesome server!\"", + "\tset join message to \"Welcome %player% to our awesome server!\"", "", "on join:", - " player has played before", - " set join message to \"Welcome back, %player%!\"", + "\tplayer has played before", + "\tset join message to \"Welcome back, %player%!\"", "", "on quit:", - " set quit message to \"%player% left this awesome server!\"", + "\tif {vanish::%player's uuid%} is set:", + "\t\tclear quit message", + "\telse:", + "\t\tset quit message to \"%player% left this awesome server!\"", "", "on death:", - " set the death message to \"%player% died!\""}) -@Since("1.4.6 (chat message), 1.4.9 (join & quit messages), 2.0 (death message)") + "\tset the death message to \"%player% died!\""}) +@Since("1.4.6 (chat message), 1.4.9 (join & quit messages), 2.0 (death message), 2.9.0 (clear message)") @Events({"chat", "join", "quit", "death"}) public class ExprMessage extends SimpleExpression { @@ -181,18 +185,18 @@ protected String[] get(final Event e) { @Override @Nullable public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.SET) + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) return CollectionUtils.array(String.class); return null; } @Override public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - assert mode == ChangeMode.SET; - assert delta != null; + assert mode == ChangeMode.SET || mode == ChangeMode.DELETE; for (final Class c : type.events) { - if (c.isInstance(e)) - type.set(e, "" + delta[0]); + if (c.isInstance(e)) { + type.set(e, (mode == ChangeMode.DELETE) ? "" : delta[0].toString()); + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprName.java b/src/main/java/ch/njol/skript/expressions/ExprName.java index b6f873e0b89..1d5dd354e0c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprName.java +++ b/src/main/java/ch/njol/skript/expressions/ExprName.java @@ -18,12 +18,11 @@ */ package ch.njol.skript.expressions; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.List; +import ch.njol.skript.bukkitutil.InventoryUtils; +import ch.njol.skript.bukkitutil.ItemUtils; import org.bukkit.Bukkit; import org.bukkit.GameRule; import org.bukkit.Nameable; @@ -44,7 +43,6 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -60,7 +58,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; @@ -139,7 +136,6 @@ public class ExprName extends SimplePropertyExpression { * 3 = "tablist name" */ private int mark; - private static final ItemType AIR = Aliases.javaItemType("air"); @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { @@ -182,7 +178,7 @@ public String convert(Object object) { Inventory inventory = (Inventory) object; if (inventory.getViewers().isEmpty()) return null; - return inventory.getViewers().get(0).getOpenInventory().getTitle(); + return InventoryUtils.getTitle(inventory.getViewers().get(0).getOpenInventory()); } else if (object instanceof Slot) { ItemStack is = ((Slot) object).getItem(); if (is != null && is.hasItemMeta()) { @@ -282,7 +278,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { } else if (object instanceof Slot) { Slot s = (Slot) object; ItemStack is = s.getItem(); - if (is != null && !AIR.isOfType(is)) { + if (is != null && !ItemUtils.isAir(is.getType())) { ItemMeta m = is.hasItemMeta() ? is.getItemMeta() : Bukkit.getItemFactory().getItemMeta(is.getType()); m.setDisplayName(name); is.setItemMeta(m); diff --git a/src/main/java/ch/njol/skript/expressions/ExprOpenedInventory.java b/src/main/java/ch/njol/skript/expressions/ExprOpenedInventory.java index d6f09b7d9d4..b8390e160d3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOpenedInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOpenedInventory.java @@ -18,9 +18,11 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.bukkitutil.InventoryUtils; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -61,10 +63,12 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final @Override protected Inventory[] get(Event event, Player[] source) { return get(source, new Getter() { - @SuppressWarnings("null") @Override - public Inventory get(final Player player) { - return player.getOpenInventory() != null ? player.getOpenInventory().getTopInventory() : null; + public @Nullable Inventory get(final Player player) { + InventoryView openInventory = player.getOpenInventory(); + if (openInventory == null) + return null; + return InventoryUtils.getTopInventory(openInventory); } }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlain.java b/src/main/java/ch/njol/skript/expressions/ExprPlain.java index ca73ba22820..67fb7ca3086 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPlain.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPlain.java @@ -32,6 +32,7 @@ import ch.njol.util.Kleenean; import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; @Name("Plain Item") @@ -61,7 +62,7 @@ protected ItemType[] get(Event e) { ItemType itemType = item.getSingle(e); if (itemType == null) return new ItemType[0]; - ItemData data = new ItemData(itemType.getRandom().getType()); + ItemData data = new ItemData(itemType.getMaterial()); data.setPlain(true); ItemType plain = new ItemType(data); return new ItemType[]{plain}; diff --git a/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java b/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java index 57b07d4d641..9c2d5f0e036 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java @@ -1,28 +1,5 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.expressions; -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -30,79 +7,75 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; -/** - * @author Peter Güttinger - */ @Name("Remaining Air") @Description("How much time a player has left underwater before starting to drown.") -@Examples({"player's remaining air is less than 3 seconds:", - " send \"hurry, get to the surface!\" to the player"}) -@Since("unknown (before 2.1)") +@Examples({ + "if the player's remaining air is less than 3 seconds:", + "\tsend \"hurry, get to the surface!\" to the player" +}) +@Since("2.0") public class ExprRemainingAir extends SimplePropertyExpression { static { register(ExprRemainingAir.class, Timespan.class, "remaining air", "livingentities"); } - - @Override - public Class getReturnType() { - return Timespan.class; - } - - @Override - protected String getPropertyName() { - return "remaining air"; - } - - @Override - public Timespan convert(final LivingEntity entity) { - return Timespan.fromTicks(entity.getRemainingAir()); - } - - @Nullable + @Override - public Class[] acceptChange(Changer.ChangeMode mode) { - return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Timespan.class) : null; + public Timespan convert(LivingEntity entity) { + /* + * negative values are allowed, and Minecraft itself may return a negative value from -1 to -20 + * these negative values seem to control when the entity actually takes damage + * that is, when it hits -20, the entity takes damage, and it goes back to 0 + * for simplicity, we cap it at 0 seconds (as it is still the case that the entity has no air) + */ + return new Timespan(TimePeriod.TICK, Math.max(0, entity.getRemainingAir())); } - - @SuppressWarnings("null") + @Override - public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { switch (mode) { case ADD: - long ticks = ((Timespan)delta[0]).getTicks(); - for (LivingEntity entity : getExpr().getArray(event)) { - int newTicks = entity.getRemainingAir() + (int) ticks; - - // Sanitize remaining air to avoid client hangs/crashes - if (newTicks > 20000) // 1000 seconds - newTicks = 20000; - entity.setRemainingAir(newTicks); - } - break; - case REMOVE: - ticks = ((Timespan)delta[0]).getTicks(); - for (LivingEntity entity : getExpr().getArray(event)) - entity.setRemainingAir(entity.getRemainingAir() - (int) ticks); - break; case SET: - ticks = ((Timespan)delta[0]).getTicks(); - // Sanitize remaining air to avoid client hangs/crashes - if (ticks > 20000) // 1000 seconds - ticks = 20000; - - for (LivingEntity entity : getExpr().getArray(event)) - entity.setRemainingAir((int) ticks); - break; + case REMOVE: case DELETE: - case REMOVE_ALL: case RESET: - for (LivingEntity entity : getExpr().getArray(event)) - entity.setRemainingAir(20 * 15); // 15 seconds of air - break; + return CollectionUtils.array(Timespan.class); + default: + return null; } } - + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + // default is 15 seconds of air + long changeValue = delta != null ? ((Timespan) delta[0]).getAs(TimePeriod.TICK) : 20 * 15; + if (mode == ChangeMode.REMOVE) // subtract the change value + changeValue *= -1; + for (LivingEntity entity : getExpr().getArray(event)) { + long newRemainingAir = 0; + if (mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) + newRemainingAir = entity.getRemainingAir(); + // while entities have a "maximum air", the value is allowed to go past it + // while negative values are permitted, the behavior is strange + newRemainingAir = Math.max(Math.min(newRemainingAir + changeValue, Integer.MAX_VALUE), 0); + entity.setRemainingAir((int) newRemainingAir); + } + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "remaining air"; + } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java b/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java index 58426835823..64a61a8c96b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java @@ -37,7 +37,7 @@ }) @Examples("broadcast \"The bell has been resonating for %resonating time of target block%\"") @RequiredPlugins("Spigot 1.19.4+") -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprResonatingTime extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprRingingTime.java b/src/main/java/ch/njol/skript/expressions/ExprRingingTime.java index 482f7e0c2cf..1a67416abc0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRingingTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRingingTime.java @@ -37,7 +37,7 @@ }) @Examples("broadcast \"The bell has been ringing for %ringing time of target block%\"") @RequiredPlugins("Spigot 1.19.4+") -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprRingingTime extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSignText.java b/src/main/java/ch/njol/skript/expressions/ExprSignText.java index 592420ed647..0b549483b17 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSignText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSignText.java @@ -18,7 +18,10 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.bukkitutil.ItemUtils; +import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; import org.bukkit.block.Sign; import org.bukkit.event.Event; import org.bukkit.event.block.SignChangeEvent; @@ -56,8 +59,6 @@ public class ExprSignText extends SimpleExpression { "[the] line %number% [of %block%]", "[the] (1¦1st|1¦first|2¦2nd|2¦second|3¦3rd|3¦third|4¦4th|4¦fourth) line [of %block%]"); } - private static final ItemType sign = Aliases.javaItemType("sign"); - @SuppressWarnings("null") private Expression line; @SuppressWarnings("null") @@ -99,7 +100,7 @@ protected String[] get(final Event e) { final Block b = block.getSingle(e); if (b == null) return new String[0]; - if (!sign.isOfType(b)) + if (!(b.getState() instanceof Sign)) return new String[0]; return new String[] {((Sign) b.getState()).getLine(line)}; } @@ -143,27 +144,28 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo break; } } else { - if (!sign.isOfType(b)) + BlockState state = b.getState(); + if (!(state instanceof Sign)) return; - final Sign s = (Sign) b.getState(); + Sign sign = (Sign) b.getState(); switch (mode) { case DELETE: - s.setLine(line, ""); + sign.setLine(line, ""); break; case SET: assert delta != null; - s.setLine(line, (String) delta[0]); + sign.setLine(line, (String) delta[0]); break; } if (hasUpdateBooleanBoolean) { try { - s.update(false, false); + sign.update(false, false); } catch (final NoSuchMethodError err) { hasUpdateBooleanBoolean = false; - s.update(); + sign.update(); } } else { - s.update(); + sign.update(); } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkull.java b/src/main/java/ch/njol/skript/expressions/ExprSkull.java index e3a0fa2767d..4e133a79bfd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSkull.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSkull.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.expressions; +import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.meta.SkullMeta; import org.eclipse.jdt.annotation.Nullable; @@ -48,8 +49,6 @@ public class ExprSkull extends SimplePropertyExpression { register(ExprSkull.class, ItemType.class, "(head|skull)", "offlineplayers"); } - private static final ItemType playerSkull = Aliases.javaItemType("player skull"); - /** * In 2017, SkullMeta finally got a method that takes OfflinePlayer. */ @@ -64,7 +63,7 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final @Override @Nullable public ItemType convert(final Object o) { - ItemType skull = playerSkull.clone(); + ItemType skull = new ItemType(Material.PLAYER_HEAD); SkullMeta meta = (SkullMeta) skull.getItemMeta(); if (newSkullOwner) meta.setOwningPlayer((OfflinePlayer) o); diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java new file mode 100644 index 00000000000..5ef700abeb4 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Skull Owner") +@Description("The skull owner of a player skull.") +@Examples({ + "set {_owner} to the skull owner of event-block", + "set skull owner of {_block} to \"Njol\" parsed as offlineplayer" +}) +@Since("2.9.0") +public class ExprSkullOwner extends SimplePropertyExpression { + + static { + register(ExprSkullOwner.class, OfflinePlayer.class, "(head|skull) owner", "blocks"); + } + + @Override + public @Nullable OfflinePlayer convert(Block block) { + BlockState state = block.getState(); + if (!(state instanceof Skull)) + return null; + return ((Skull) state).getOwningPlayer(); + } + + @Override + @Nullable + public Class[] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(OfflinePlayer.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + OfflinePlayer offlinePlayer = (OfflinePlayer) delta[0]; + for (Block block : getExpr().getArray(event)) { + BlockState state = block.getState(); + if (state instanceof Skull) { + Skull skull = (Skull) state; + skull.setOwningPlayer(offlinePlayer); + skull.update(true, false); + } + } + } + + @Override + public Class getReturnType() { + return OfflinePlayer.class; + } + + @Override + protected String getPropertyName() { + return "skull owner"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java b/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java index d9d31b824cc..24b59ae9dd3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java @@ -22,6 +22,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.EntityType; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -44,18 +45,19 @@ @Since("2.4") public class ExprSpawnerType extends SimplePropertyExpression { - private static final Material MATERIAL_SPAWNER = Aliases.javaItemType("spawner").getMaterial(); - static { register(ExprSpawnerType.class, EntityData.class, "(spawner|entity|creature) type[s]", "blocks"); } @Override @Nullable - public EntityData convert(final Block b) { - if (b.getType() != MATERIAL_SPAWNER) + public EntityData convert(Block block) { + if (!(block.getState() instanceof CreatureSpawner)) + return null; + EntityType type = ((CreatureSpawner) block.getState()).getSpawnedType(); + if (type == null) return null; - return EntityUtils.toSkriptEntityData(((CreatureSpawner) b.getState()).getSpawnedType()); + return EntityUtils.toSkriptEntityData(type); } @Nullable @@ -68,13 +70,14 @@ public Class[] acceptChange(Changer.ChangeMode mode) { @SuppressWarnings("null") @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - for (Block b : getExpr().getArray(e)) { - if (b.getType() != MATERIAL_SPAWNER) + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + for (Block b : getExpr().getArray(event)) { + if (!(b.getState() instanceof CreatureSpawner)) continue; CreatureSpawner s = (CreatureSpawner) b.getState(); switch (mode) { case SET: + assert delta != null; s.setSpawnedType(EntityUtils.toBukkitEntityType((EntityData) delta[0])); break; case RESET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java b/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java index 9d18734c3fd..11a64ad313b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTargetedBlock.java @@ -50,7 +50,7 @@ "set {_block} to actual target block of player", "break actual target block of player" }) -@Since("1.0, INSERT VERSION (actual/exact)") +@Since("1.0, 2.9.0 (actual/exact)") public class ExprTargetedBlock extends PropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java index a699d6e6e95..26b72f49d61 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java @@ -38,7 +38,7 @@ "set {_t} to difference between now and {Payouts::players::%uuid of player%::last-date}", "send \"It has been %days of {_t}% day(s) since last payout.\"" }) -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprTimespanDetails extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java index dabbd00dd5c..bda14bb13a0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java @@ -37,7 +37,7 @@ "set {_item} to unbreakable iron sword", "give breakable {_weapon} to all players" }) -@Since("2.2-dev13b, INSERT VERSION (breakable)") +@Since("2.2-dev13b, 2.9.0 (breakable)") public class ExprUnbreakable extends SimplePropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprWhether.java b/src/main/java/ch/njol/skript/expressions/ExprWhether.java index 4817551e181..1b4050f0068 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWhether.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWhether.java @@ -38,7 +38,7 @@ "set {fly} to whether player can fly", "broadcast \"Flying: %whether player is flying%\"" }) -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprWhether extends SimpleExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java b/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java index e85a7ce345a..3ed53d44995 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWhitelist.java @@ -49,7 +49,7 @@ "add all players to whitelist", "reset the whitelist" }) -@Since("2.5.2, INSERT VERSION (delete)") +@Since("2.5.2, 2.9.0 (delete)") public class ExprWhitelist extends SimpleExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java index e471b4610ee..2f482bbc493 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java @@ -44,7 +44,7 @@ "drop fire resistant stone at player" }) @RequiredPlugins("Spigot 1.20.5+") -@Since("INSERT VERSION") +@Since("2.9.0") public class ExprWithFireResistance extends PropertyExpression { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprYawPitch.java b/src/main/java/ch/njol/skript/expressions/ExprYawPitch.java index cbbc3ba085d..1e7103834e6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprYawPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprYawPitch.java @@ -18,8 +18,13 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.ServerPlatform; +import ch.njol.skript.Skript; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.util.VectorMath; import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.Event; import ch.njol.skript.classes.Changer.ChangeMode; @@ -33,125 +38,193 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; @Name("Yaw / Pitch") -@Description("The yaw or pitch of a location or vector.") -@Examples({"log \"%player%: %location of player%, %player's yaw%, %player's pitch%\" to \"playerlocs.log\"", - "set {_yaw} to yaw of player", - "set {_p} to pitch of target entity"}) -@Since("2.0, 2.2-dev28 (vector yaw/pitch)") -public class ExprYawPitch extends SimplePropertyExpression { +@Description({ + "The yaw or pitch of a location or vector.", + "A yaw of 0 or 360 represents the positive z direction. Adding a positive number to the yaw of a player will rotate it clockwise.", + "A pitch of 90 represents the negative y direction, or downward facing. A pitch of -90 represents upward facing. Adding a positive number to the pitch will rotate the direction downwards.", + "Only Paper 1.19+ users may directly change the yaw/pitch of players." +}) +@Examples({ + "log \"%player%: %location of player%, %player's yaw%, %player's pitch%\" to \"playerlocs.log\"", + "set {_yaw} to yaw of player", + "set {_p} to pitch of target entity", + "set pitch of player to -90 # Makes the player look upwards, Paper 1.19+ only", + "add 180 to yaw of target of player # Makes the target look behind themselves" +}) +@Since("2.0, 2.2-dev28 (vector yaw/pitch), 2.9.0 (entity changers)") +@RequiredPlugins("Paper 1.19+ (player changers)") +public class ExprYawPitch extends SimplePropertyExpression { static { - register(ExprYawPitch.class, Number.class, "(0¦yaw|1¦pitch)", "locations/vectors"); + register(ExprYawPitch.class, Float.class, "(:yaw|pitch)", "entities/locations/vectors"); } + // For non-Paper versions lower than 1.19, changing the rotation of an entity is not supported for players. + private static final boolean SUPPORTS_PLAYERS = Skript.isRunningMinecraft(1, 19) && Skript.getServerPlatform() == ServerPlatform.BUKKIT_PAPER; + private boolean usesYaw; @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - usesYaw = parseResult.mark == 0; + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + usesYaw = parseResult.hasTag("yaw"); return super.init(exprs, matchedPattern, isDelayed, parseResult); } @Override - public Number convert(final Object object) { - if (object instanceof Location) { - Location l = ((Location) object); - return usesYaw ? convertToPositive(l.getYaw()) : l.getPitch(); + public Float convert(Object object) { + if (object instanceof Entity) { + Location location = ((Entity) object).getLocation(); + return usesYaw + ? normalizeYaw(location.getYaw()) + : location.getPitch(); + } else if (object instanceof Location) { + Location location = (Location) object; + return usesYaw + ? normalizeYaw(location.getYaw()) + : location.getPitch(); } else if (object instanceof Vector) { - Vector vector = ((Vector) object); - if (usesYaw) - return VectorMath.skriptYaw(VectorMath.getYaw(vector)); - return VectorMath.skriptPitch(VectorMath.getPitch(vector)); + Vector vector = (Vector) object; + return usesYaw + ? VectorMath.skriptYaw((VectorMath.getYaw(vector))) + : VectorMath.skriptPitch(VectorMath.getPitch(vector)); } return null; } - @SuppressWarnings({"null"}) @Override - public Class[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) - return CollectionUtils.array(Number.class); - return null; + public Class[] acceptChange(ChangeMode mode) { + if (Player.class.isAssignableFrom(getExpr().getReturnType()) && !SUPPORTS_PLAYERS) + return null; + + switch (mode) { + case SET: + case ADD: + case REMOVE: + return CollectionUtils.array(Number.class); + case RESET: + return new Class[0]; + default: + return null; + } } - @SuppressWarnings("null") @Override - public void change(Event e, Object[] delta, ChangeMode mode) { - if (delta == null) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null && mode != ChangeMode.RESET) return; float value = ((Number) delta[0]).floatValue(); - for (Object single : getExpr().getArray(e)) { - if (single instanceof Location) { - changeLocation(((Location) single), value, mode); - } else if (single instanceof Vector) { - changeVector(((Vector) single), value, mode); + for (Object object : getExpr().getArray(event)) { + if (object instanceof Player && !SUPPORTS_PLAYERS) + continue; + + if (object instanceof Entity) { + changeForEntity((Entity) object, value, mode); + } else if (object instanceof Location) { + changeForLocation(((Location) object), value, mode); + } else if (object instanceof Vector) { + changeForVector(((Vector) object), value, mode); } } } - private void changeLocation(Location l, float value, ChangeMode mode) { + private void changeForEntity(Entity entity, float value, ChangeMode mode) { + Location location = entity.getLocation(); switch (mode) { case SET: - if (usesYaw) - l.setYaw(convertToPositive(value)); - else - l.setPitch(value); + if (usesYaw) { + entity.setRotation(value, location.getPitch()); + } else { + entity.setRotation(location.getYaw(), value); + } break; + case REMOVE: + value = -value; case ADD: - if (usesYaw) - l.setYaw(convertToPositive(l.getYaw()) + value); - else - l.setPitch(l.getPitch() + value); + if (usesYaw) { + entity.setRotation(location.getYaw() + value, location.getPitch()); + } else { + // Subtracting because of Minecraft's upside-down pitch. + entity.setRotation(location.getYaw(), location.getPitch() - value); + } + break; + case RESET: + if (usesYaw) { + entity.setRotation(0, location.getPitch()); + } else { + entity.setRotation(location.getYaw(), 0); + } + break; + default: + break; + } + } + + private void changeForLocation(Location location, float value, ChangeMode mode) { + switch (mode) { + case SET: + if (usesYaw) { + location.setYaw(value); + } else { + location.setPitch(value); + } break; case REMOVE: - if (usesYaw) - l.setYaw(convertToPositive(l.getYaw()) - value); - else - l.setPitch(l.getPitch() - value); + value = -value; + case ADD: + if (usesYaw) { + location.setYaw(location.getYaw() + value); + } else { + // Subtracting because of Minecraft's upside-down pitch. + location.setPitch(location.getPitch() - value); + } break; + case RESET: + if (usesYaw) { + location.setYaw(0); + } else { + location.setPitch(0); + } default: break; } } - private void changeVector(Vector vector, float n, ChangeMode mode) { + private void changeForVector(Vector vector, float value, ChangeMode mode) { float yaw = VectorMath.getYaw(vector); float pitch = VectorMath.getPitch(vector); switch (mode) { case REMOVE: - n = -n; - //$FALL-THROUGH$ + value = -value; + // $FALL-THROUGH$ case ADD: - if (usesYaw) - yaw += n; - else - pitch -= n; // Negative because of Minecraft's / Skript's upside down pitch - Vector newVector = VectorMath.fromYawAndPitch(yaw, pitch).multiply(vector.length()); - VectorMath.copyVector(vector, newVector); + if (usesYaw) { + yaw += value; + } else { + // Subtracting because of Minecraft's upside-down pitch. + pitch -= value; + } break; case SET: if (usesYaw) - yaw = VectorMath.fromSkriptYaw(n); + yaw = VectorMath.fromSkriptYaw(value); else - pitch = VectorMath.fromSkriptPitch(n); - newVector = VectorMath.fromYawAndPitch(yaw, pitch).multiply(vector.length()); - VectorMath.copyVector(vector, newVector); + pitch = VectorMath.fromSkriptPitch(value); } + Vector newVector = VectorMath.fromYawAndPitch(yaw, pitch).multiply(vector.length()); + VectorMath.copyVector(vector, newVector); } - - //Some random method decided to use for converting to positive values. - public float convertToPositive(float f) { - if (f != 0 && f * -1 == Math.abs(f)) - return 360 + f; - return f; + private static float normalizeYaw(float yaw) { + yaw = Location.normalizeYaw(yaw); + return yaw < 0 ? yaw + 360 : yaw; } @Override - public Class getReturnType() { - return Number.class; + public Class getReturnType() { + return Float.class; } @Override diff --git a/src/main/java/ch/njol/skript/expressions/LitNewLine.java b/src/main/java/ch/njol/skript/expressions/LitNewLine.java index d6ca312fb6d..fbe845946a3 100644 --- a/src/main/java/ch/njol/skript/expressions/LitNewLine.java +++ b/src/main/java/ch/njol/skript/expressions/LitNewLine.java @@ -19,7 +19,6 @@ package ch.njol.skript.expressions; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; @@ -28,9 +27,10 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.util.Kleenean; +import org.jetbrains.annotations.Nullable; @Name("New Line") @Description("Returns a line break separator.") @@ -39,7 +39,7 @@ public class LitNewLine extends SimpleLiteral { static { - Skript.registerExpression(LitNewLine.class, String.class, ExpressionType.SIMPLE, "n[ew]l[ine]", "line[ ]break"); + Skript.registerExpression(LitNewLine.class, String.class, ExpressionType.SIMPLE, "nl", "new[ ]line", "line[ ]break"); } public LitNewLine() { @@ -47,12 +47,12 @@ public LitNewLine() { } @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parseResult) { + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult result) { return true; } @Override - public String toString(@Nullable Event e, boolean debug) { - return "newline"; + public String toString(@Nullable Event event, boolean debug) { + return "new line"; } } diff --git a/src/main/java/ch/njol/skript/lang/Condition.java b/src/main/java/ch/njol/skript/lang/Condition.java index c4d67cc5fa4..3388f43bbc0 100644 --- a/src/main/java/ch/njol/skript/lang/Condition.java +++ b/src/main/java/ch/njol/skript/lang/Condition.java @@ -22,7 +22,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Checker; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; @@ -67,12 +67,11 @@ public final boolean isNegated() { return negated; } - @Nullable - @SuppressWarnings({"rawtypes", "unchecked"}) - public static Condition parse(String input, @Nullable String defaultError) { + public static @Nullable Condition parse(String input, @Nullable String defaultError) { input = input.trim(); while (input.startsWith("(") && SkriptParser.next(input, 0, ParseContext.DEFAULT) == input.length()) input = input.substring(1, input.length() - 1); + //noinspection unchecked,rawtypes return (Condition) SkriptParser.parse(input, (Iterator) Skript.getConditions().iterator(), defaultError); } diff --git a/src/main/java/ch/njol/skript/lang/Debuggable.java b/src/main/java/ch/njol/skript/lang/Debuggable.java index 3de15921825..98714194d32 100644 --- a/src/main/java/ch/njol/skript/lang/Debuggable.java +++ b/src/main/java/ch/njol/skript/lang/Debuggable.java @@ -19,7 +19,7 @@ package ch.njol.skript.lang; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; /** * Represents an element that can print details involving an event. diff --git a/src/main/java/ch/njol/skript/lang/Effect.java b/src/main/java/ch/njol/skript/lang/Effect.java index 0b734ad4372..e8d2338313a 100644 --- a/src/main/java/ch/njol/skript/lang/Effect.java +++ b/src/main/java/ch/njol/skript/lang/Effect.java @@ -23,7 +23,7 @@ import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; @@ -50,11 +50,8 @@ public final boolean run(Event event) { return true; } - @Nullable - @SuppressWarnings({"rawtypes", "unchecked"}) - public static Effect parse(String input, @Nullable String defaultError) { - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + public static @Nullable Effect parse(String input, @Nullable String defaultError) { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { EffFunctionCall functionCall = EffFunctionCall.parse(input); if (functionCall != null) { log.printLog(); @@ -72,6 +69,7 @@ public static Effect parse(String input, @Nullable String defaultError) { } log.clear(); + //noinspection unchecked,rawtypes Effect effect = (Effect) SkriptParser.parse(input, (Iterator) Skript.getEffects().iterator(), defaultError); if (effect != null) { log.printLog(); @@ -80,8 +78,6 @@ public static Effect parse(String input, @Nullable String defaultError) { log.printError(); return null; - } finally { - log.stop(); } } diff --git a/src/main/java/ch/njol/skript/lang/EffectSection.java b/src/main/java/ch/njol/skript/lang/EffectSection.java index 4da3a5a5bc5..070812277c6 100644 --- a/src/main/java/ch/njol/skript/lang/EffectSection.java +++ b/src/main/java/ch/njol/skript/lang/EffectSection.java @@ -23,7 +23,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.util.Kleenean; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; @@ -69,11 +69,10 @@ public abstract boolean init(Expression[] expressions, /** * Similar to {@link Section#parse(String, String, SectionNode, List)}, but will only attempt to parse from other {@link EffectSection}s. */ - @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) - public static EffectSection parse(String input, @Nullable String defaultError, @Nullable SectionNode sectionNode, @Nullable List triggerItems) { + public static @Nullable EffectSection parse(String input, @Nullable String defaultError, @Nullable SectionNode sectionNode, @Nullable List triggerItems) { SectionContext sectionContext = ParserInstance.get().getData(SectionContext.class); + //noinspection unchecked,rawtypes return sectionContext.modify(sectionNode, triggerItems, () -> (EffectSection) SkriptParser.parse( input, diff --git a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java index 2b93bef0b60..3cc1916e700 100644 --- a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java +++ b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java @@ -21,7 +21,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; /** * Represents the Effect aspect of an EffectSection. This allows for the use of EffectSections as effects, rather than just sections. diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index 9c7439f7585..4be74231f5e 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -31,8 +31,7 @@ import ch.njol.util.Checker; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; import java.util.HashMap; @@ -64,8 +63,7 @@ public interface Expression extends SyntaxElement, Debuggable { * @return The value or null if this expression doesn't have any value for the event * @throws UnsupportedOperationException (optional) if this was called on a non-single expression */ - @Nullable - T getSingle(Event event); + @Nullable T getSingle(Event event); /** * Get an optional of the single value of this expression. @@ -107,7 +105,7 @@ default Optional getOptionalSingle(Event event) { * @param event The event * @return A non-null stream of this expression's non-null values */ - default Stream<@NonNull ? extends T> stream(Event event) { + default Stream stream(Event event) { Iterator iterator = iterator(event); if (iterator == null) { return Stream.empty(); @@ -178,9 +176,8 @@ default boolean canBeSingle() { * @see Converter * @see ConvertedExpression */ - @Nullable @SuppressWarnings("unchecked") - Expression getConvertedExpression(Class... to); + @Nullable Expression getConvertedExpression(Class... to); /** * Gets the return type of this expression. @@ -265,8 +262,7 @@ default boolean canReturn(Class returnType) { * @param event The event to be used for evaluation * @return An iterator to iterate over all values of this expression which may be empty and/or null, but must not return null elements. */ - @Nullable - Iterator iterator(Event event); + @Nullable Iterator iterator(Event event); /** * Checks whether the given 'loop-...' expression should match this loop, e.g. loop-block matches any loops that loop through blocks and loop-argument matches an @@ -316,8 +312,7 @@ default boolean canReturn(Class returnType) { * that type are accepted), or null if the given mode is not supported. For {@link ChangeMode#DELETE} and {@link ChangeMode#RESET} this can return any non-null array to * mark them as supported. */ - @Nullable - Class[] acceptChange(ChangeMode mode); + Class @Nullable [] acceptChange(ChangeMode mode); /** * Tests all accepted change modes, and if so what type it expects the delta to be. @@ -343,7 +338,7 @@ default Map[]> getAcceptedChangeModes() { * @param mode The {@link ChangeMode} of the attempted change * @throws UnsupportedOperationException (optional) - If this method was called on an unsupported ChangeMode. */ - void change(Event event, @Nullable Object[] delta, ChangeMode mode); + void change(Event event, Object @Nullable [] delta, ChangeMode mode); /** * This method is called before this expression is set to another one. @@ -356,8 +351,7 @@ default Map[]> getAcceptedChangeModes() { * @param delta Initial delta array. * @return Delta array to use for change. */ - @Nullable - default Object[] beforeChange(Expression changed, @Nullable Object[] delta) { + default Object @Nullable [] beforeChange(Expression changed, Object @Nullable [] delta) { if (delta == null || delta.length == 0) // Nothing to nothing return null; diff --git a/src/main/java/ch/njol/skript/lang/ExpressionInfo.java b/src/main/java/ch/njol/skript/lang/ExpressionInfo.java index 0c2c0c2bc62..2d4f1ec83c0 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionInfo.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionInfo.java @@ -18,15 +18,14 @@ */ package ch.njol.skript.lang; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; /** * Represents an expression's information, for use when creating new instances of expressions. */ public class ExpressionInfo, T> extends SyntaxElementInfo { - @Nullable - public ExpressionType expressionType; + public @Nullable ExpressionType expressionType; public Class returnType; public ExpressionInfo(String[] patterns, Class returnType, Class expressionClass, String originClassPath) throws IllegalArgumentException { @@ -51,8 +50,7 @@ public Class getReturnType() { * Get the type of this expression. * @return The type of this Expression */ - @Nullable - public ExpressionType getExpressionType() { + public @Nullable ExpressionType getExpressionType() { return expressionType; } diff --git a/src/main/java/ch/njol/skript/lang/ExpressionList.java b/src/main/java/ch/njol/skript/lang/ExpressionList.java index fa6612b3422..9c89358431d 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionList.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionList.java @@ -27,7 +27,7 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Array; import java.util.ArrayList; @@ -47,8 +47,7 @@ public class ExpressionList implements Expression { protected boolean and; private final boolean single; - @Nullable - private final ExpressionList source; + private final @Nullable ExpressionList source; public ExpressionList(Expression[] expressions, Class returnType, boolean and) { this(expressions, returnType, and, null); @@ -89,8 +88,7 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is } @Override - @Nullable - public T getSingle(Event event) { + public @Nullable T getSingle(Event event) { if (!single) throw new UnsupportedOperationException(); Expression expression = CollectionUtils.getRandom(expressions); @@ -98,31 +96,30 @@ public T getSingle(Event event) { } @Override - @SuppressWarnings("unchecked") public T[] getArray(Event event) { if (and) return getAll(event); Expression expression = CollectionUtils.getRandom(expressions); + //noinspection unchecked return expression != null ? expression.getArray(event) : (T[]) Array.newInstance(returnType, 0); } @Override - @SuppressWarnings("unchecked") public T[] getAll(Event event) { List values = new ArrayList<>(); for (Expression expr : expressions) values.addAll(Arrays.asList(expr.getAll(event))); + //noinspection unchecked return values.toArray((T[]) Array.newInstance(returnType, values.size())); } @Override - @Nullable - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { if (!and) { Expression expression = CollectionUtils.getRandom(expressions); return expression != null ? expression.iterator(event) : null; } - return new Iterator() { + return new Iterator<>() { private int i = 0; @Nullable private Iterator current = null; @@ -178,9 +175,8 @@ public boolean check(Event event, Checker checker) { } @Override - @Nullable @SuppressWarnings("unchecked") - public Expression getConvertedExpression(Class... to) { + public @Nullable Expression getConvertedExpression(Class... to) { Expression[] exprs = new Expression[expressions.length]; Class[] returnTypes = new Class[expressions.length]; for (int i = 0; i < exprs.length; i++) { @@ -215,8 +211,7 @@ public void invertAnd() { } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { Class[] exprClasses = expressions[0].acceptChange(mode); if (exprClasses == null) return null; @@ -234,7 +229,7 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) throws UnsupportedOperationException { for (Expression expr : expressions) { expr.change(event, delta, mode); } diff --git a/src/main/java/ch/njol/skript/lang/InputSource.java b/src/main/java/ch/njol/skript/lang/InputSource.java new file mode 100644 index 00000000000..f996557bcdc --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/InputSource.java @@ -0,0 +1,99 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang; + +import ch.njol.skript.expressions.ExprInput; +import ch.njol.skript.lang.parser.ParserInstance; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.Set; + +/** + * An InputSource represents a syntax that can provide a + * value for {@link ExprInput} to use. + *
+ * @see ch.njol.skript.expressions.ExprFilter + * @see ch.njol.skript.effects.EffSort + */ +public interface InputSource { + + /** + * @return A mutable {@link Set} of {@link ExprInput}s that depend on this source. + */ + Set> getDependentInputs(); + + /** + * @return The current value that {@link ExprInput} should use. + */ + @Nullable Object getCurrentValue(); + + /** + * {@link InputSource}s that can supply indices along with values should override this + * method to indicate their ability. + * + * @return Whether this source can return indices. + */ + default boolean hasIndices() { + return false; + } + + /** + * This should only be used by {@link InputSource}s that return true for {@link InputSource#hasIndices()}. + * + * @return The current value's index. + */ + default @UnknownNullability String getCurrentIndex() { + return null; + } + + /** + * A {@link ch.njol.skript.lang.parser.ParserInstance.Data} used for + * linking {@link InputSource}s and {@link ExprInput}s. + */ + class InputData extends ParserInstance.Data { + + private @Nullable InputSource source; + + public InputData(ParserInstance parserInstance) { + super(parserInstance); + } + + /** + * {@link InputSource} should call this during init() to declare that they are the current source for future + * {@link ExprInput}s, and then reset it to its previous value once out of scope. + * + * @param source the source of information. + */ + public void setSource(@Nullable InputSource source) { + this.source = source; + } + + /** + * ExprInput should use this to get the information source, and then call + * {@link InputSource#getCurrentValue()} to get the current value of the source. + * + * @return the source of information. + */ + public @Nullable InputSource getSource() { + return source; + } + + } +} diff --git a/src/main/java/ch/njol/skript/lang/Literal.java b/src/main/java/ch/njol/skript/lang/Literal.java index 890b41b2e12..c3fe27cf0aa 100644 --- a/src/main/java/ch/njol/skript/lang/Literal.java +++ b/src/main/java/ch/njol/skript/lang/Literal.java @@ -18,7 +18,7 @@ */ package ch.njol.skript.lang; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; /** * A literal, e.g. a number, string or item. Literals are constants which do not depend on the event and can thus e.g. be used in events. @@ -32,9 +32,8 @@ public interface Literal extends Expression { T getSingle(); @Override - @Nullable @SuppressWarnings("unchecked") - Literal getConvertedExpression(Class... to); + @Nullable Literal getConvertedExpression(Class... to); T[] getAll(); diff --git a/src/main/java/ch/njol/skript/lang/LiteralList.java b/src/main/java/ch/njol/skript/lang/LiteralList.java index f432bd1c095..df2d8d51966 100644 --- a/src/main/java/ch/njol/skript/lang/LiteralList.java +++ b/src/main/java/ch/njol/skript/lang/LiteralList.java @@ -18,9 +18,9 @@ */ package ch.njol.skript.lang; -import ch.njol.skript.registrations.Classes; import ch.njol.skript.lang.util.SimpleLiteral; -import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.registrations.Classes; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Array; @@ -63,8 +63,8 @@ public T[] getAll() { } @Override - @Nullable - public Literal getConvertedExpression(final Class... to) { + @SuppressWarnings("unchecked") + public @Nullable Literal getConvertedExpression(final Class... to) { Literal[] exprs = new Literal[expressions.length]; Class[] returnTypes = new Class[expressions.length]; for (int i = 0; i < exprs.length; i++) { diff --git a/src/main/java/ch/njol/skript/lang/LiteralString.java b/src/main/java/ch/njol/skript/lang/LiteralString.java new file mode 100644 index 00000000000..588bd1e5691 --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/LiteralString.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang; + +import ch.njol.skript.lang.util.ConvertedLiteral; +import ch.njol.skript.util.Utils; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.Optional; + +public class LiteralString extends VariableString implements Literal { + + /** + * Creates a new VariableString which does not contain variables. + * + * @param input Content for string. + */ + protected LiteralString(String input) { + super(input); + } + + @Override + public String[] getArray() { + return new String[]{original}; + } + + @Override + public String getSingle() { + return original; + } + + @Override + public String[] getAll() { + return new String[]{original}; + } + + @Override + public Optional getOptionalSingle(Event event) { + return Optional.of(original); + } + + @Override + @SuppressWarnings("unchecked") + public @Nullable Literal getConvertedExpression(Class... to) { + if (CollectionUtils.containsSuperclass(to, String.class)) + return (Literal) this; + Class superType = (Class) Utils.getSuperType(to); + R[] parsedData = Converters.convert(this.getArray(), to, superType); + if (parsedData.length != 1) + return null; + return new ConvertedLiteral<>(this, parsedData, superType); + } + + /** + * Use {@link #toString(Event)} to get the actual string. This method is for debugging. + */ + @Override + public String toString(@Nullable Event event, boolean debug) { + return '"' + original + '"'; + } + +} diff --git a/src/main/java/ch/njol/skript/lang/ReturnHandler.java b/src/main/java/ch/njol/skript/lang/ReturnHandler.java index f908fc68c51..d905599dc4b 100644 --- a/src/main/java/ch/njol/skript/lang/ReturnHandler.java +++ b/src/main/java/ch/njol/skript/lang/ReturnHandler.java @@ -130,9 +130,11 @@ default ReturnableTrigger loadReturnableTrigger(SectionNode node, String name } /** - * @param values the values to return + * Called when {@link ch.njol.skript.effects.EffReturn} is executed + * @param event the event providing context + * @param value an expression representing the value(s) to return */ - void returnValues(T @Nullable [] values); + void returnValues(Event event, Expression value); /** * @return whether this return handler may accept multiple return values diff --git a/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java b/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java index 51c025ef70c..08b6cea873e 100644 --- a/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java +++ b/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java @@ -18,7 +18,8 @@ */ package ch.njol.skript.lang; -import org.eclipse.jdt.annotation.Nullable; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.script.Script; import java.util.Collections; @@ -36,8 +37,8 @@ public ReturnableTrigger(ReturnHandler handler, @Nullable Script script, Stri } @Override - public void returnValues(T @Nullable [] values) { - handler.returnValues(values); + public void returnValues(Event event, Expression value) { + handler.returnValues(event, value); } @Override diff --git a/src/main/java/ch/njol/skript/lang/Section.java b/src/main/java/ch/njol/skript/lang/Section.java index c511e88b2a2..6c4445c2634 100644 --- a/src/main/java/ch/njol/skript/lang/Section.java +++ b/src/main/java/ch/njol/skript/lang/Section.java @@ -25,8 +25,9 @@ import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.util.Kleenean; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Supplier; @@ -81,12 +82,17 @@ public abstract boolean init(Expression[] expressions, * (although the loaded code may change it), the calling code must deal with this. */ protected void loadCode(SectionNode sectionNode) { - List currentSections = getParser().getCurrentSections(); - currentSections.add(this); + ParserInstance parser = getParser(); + List previousSections = parser.getCurrentSections(); + + List sections = new ArrayList<>(previousSections); + sections.add(this); + parser.setCurrentSections(sections); + try { setTriggerItems(ScriptLoader.loadItems(sectionNode)); } finally { - currentSections.remove(currentSections.size() - 1); + parser.setCurrentSections(previousSections); } } @@ -166,9 +172,9 @@ protected void loadOptionalCode(SectionNode sectionNode) { } @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) public static Section parse(String expr, @Nullable String defaultError, SectionNode sectionNode, List triggerItems) { SectionContext sectionContext = ParserInstance.get().getData(SectionContext.class); + //noinspection unchecked,rawtypes return sectionContext.modify(sectionNode, triggerItems, () -> (Section) SkriptParser.parse(expr, (Iterator) Skript.getSections().iterator(), defaultError)); } diff --git a/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java b/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java index d6f56f294b6..05d8490aae7 100644 --- a/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java @@ -22,7 +22,7 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.SkriptParser.ParseResult; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; /** * To be used in sections that delay the execution of their code through a {@link Trigger}. diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index 145115a4460..ec85f9fd266 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -30,7 +30,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; @@ -55,10 +55,8 @@ public abstract class SkriptEvent extends Structure { private String expr; private SectionNode source; - @Nullable - protected EventPriority eventPriority; - @Nullable - protected ListeningBehavior listeningBehavior; + protected @Nullable EventPriority eventPriority; + protected @Nullable ListeningBehavior listeningBehavior; protected boolean supportsListeningBehavior; private SkriptEventInfo skriptEventInfo; @@ -85,8 +83,17 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par throw new IllegalStateException(); skriptEventInfo = (SkriptEventInfo) syntaxElementInfo; + assert entryContainer != null; // cannot be null for non-simple structures + this.source = entryContainer.getSource(); + + // use default value for now + listeningBehavior = eventData.getListenerBehavior(); + + // initialize implementation + if (!init(args, matchedPattern, parseResult)) + return false; + // evaluate whether this event supports listening to cancelled events - supportsListeningBehavior = false; for (Class eventClass : getEventClasses()) { if (Cancellable.class.isAssignableFrom(eventClass)) { supportsListeningBehavior = true; @@ -94,7 +101,6 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par } } - listeningBehavior = eventData.getListenerBehavior(); // if the behavior is non-null, it was set by the user if (listeningBehavior != null && !isListeningBehaviorSupported()) { String eventName = skriptEventInfo.name.toLowerCase(Locale.ENGLISH); @@ -102,10 +108,7 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par return false; } - assert entryContainer != null; // cannot be null for non-simple structures - this.source = entryContainer.getSource(); - - return init(args, matchedPattern, parseResult); + return true; } /** @@ -132,7 +135,6 @@ public boolean load() { if (!shouldLoadEvent()) return false; - // noinspection ConstantConditions - entry container cannot be null as this structure is not simple if (Skript.debug() || source.debug()) Skript.debug(expr + " (" + this + "):"); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 610b0671529..78d32c9b023 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -19,11 +19,12 @@ package ch.njol.skript.lang; import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.lang.SkriptEvent.ListeningBehavior; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.structure.StructureInfo; -import ch.njol.skript.lang.SkriptEvent.ListeningBehavior; import java.util.Locale; @@ -33,12 +34,8 @@ public final class SkriptEventInfo extends StructureInfo< public final String name; private ListeningBehavior listeningBehavior; - - @Nullable - private String[] description, examples, keywords, requiredPlugins; - - @Nullable - private String since, documentationID; + private String @Nullable [] description, examples, keywords, requiredPlugins; + private @Nullable String since, documentationID; private final String id; @@ -74,8 +71,8 @@ public SkriptEventInfo(String name, String[] patterns, Class eventClass, Stri // uses the name without 'on ' or '*' this.id = "" + name.toLowerCase(Locale.ENGLISH).replaceAll("[#'\"<>/&]", "").replaceAll("\\s+", "_"); - // default listening behavior should be to listen to uncancelled events - this.listeningBehavior = ListeningBehavior.UNCANCELLED; + // default listening behavior should be dependent on config setting + this.listeningBehavior = SkriptConfig.listenCancelledByDefault.value() ? ListeningBehavior.ANY : ListeningBehavior.UNCANCELLED; } /** @@ -179,33 +176,28 @@ public ListeningBehavior getListeningBehavior() { return listeningBehavior; } - @Nullable - public String[] getDescription() { + public String @Nullable [] getDescription() { return description; } - @Nullable - public String[] getExamples() { + public String @Nullable [] getExamples() { return examples; } - @Nullable - public String[] getKeywords() { + public String @Nullable [] getKeywords() { return keywords; } - @Nullable - public String getSince() { + public @Nullable String getSince() { return since; } - @Nullable - public String[] getRequiredPlugins() { + public String @Nullable [] getRequiredPlugins() { return requiredPlugins; } - @Nullable - public String getDocumentationID() { + public @Nullable String getDocumentationID() { return documentationID; } + } diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 3318bdb07cc..880011b4ca5 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -50,8 +50,8 @@ import ch.njol.util.coll.CollectionUtils; import com.google.common.primitives.Booleans; import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptWarning; @@ -121,8 +121,7 @@ public SkriptParser(SkriptParser other, String expr) { public static final String WILDCARD = "[^\"]*?(?:\"[^\"]*?\"[^\"]*?)*?"; public static class ParseResult { - @Nullable - public SkriptPattern source; + public @Nullable SkriptPattern source; public Expression[] exprs; public List regexes = new ArrayList<>(1); public String expr; @@ -153,12 +152,11 @@ public boolean hasTag(String tag) { *

* Prints errors. */ - @Nullable - @SuppressWarnings("unchecked") - public static Literal parseLiteral(String expr, Class expectedClass, ParseContext context) { + public static @Nullable Literal parseLiteral(String expr, Class expectedClass, ParseContext context) { expr = "" + expr.trim(); if (expr.isEmpty()) return null; + //noinspection ReassignedVariable,unchecked return new UnparsedLiteral(expr).getConvertedExpression(context, expectedClass); } @@ -167,15 +165,13 @@ public static Literal parseLiteral(String expr, Class expect *

* Can print an error. */ - @Nullable - public static T parse(String expr, Iterator> source, @Nullable String defaultError) { + public static @Nullable T parse(String expr, Iterator> source, @Nullable String defaultError) { expr = "" + expr.trim(); if (expr.isEmpty()) { Skript.error(defaultError); return null; } - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { T element = new SkriptParser(expr).parse(source); if (element != null) { log.printLog(); @@ -183,27 +179,22 @@ public static T parse(String expr, Iterator T parseStatic(String expr, Iterator> source, @Nullable String defaultError) { + public static @Nullable T parseStatic(String expr, Iterator> source, @Nullable String defaultError) { return parseStatic(expr, source, ParseContext.DEFAULT, defaultError); } - @Nullable - public static T parseStatic(String expr, Iterator> source, ParseContext parseContext, @Nullable String defaultError) { + public static @Nullable T parseStatic(String expr, Iterator> source, ParseContext parseContext, @Nullable String defaultError) { expr = expr.trim(); if (expr.isEmpty()) { Skript.error(defaultError); return null; } - ParseLogHandler log = SkriptLogger.startParseLogHandler(); T element; - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { element = new SkriptParser(expr, PARSE_LITERALS, parseContext).parse(source); if (element != null) { log.printLog(); @@ -211,15 +202,11 @@ public static T parseStatic(String expr, Iterator T parse(Iterator> source) { - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + private @Nullable T parse(Iterator> source) { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { while (source.hasNext()) { SyntaxElementInfo info = source.next(); patternsLoop: for (int patternIndex = 0; patternIndex < info.patterns.length; patternIndex++) { @@ -268,12 +255,10 @@ private T parse(Iterator @NotNull DefaultExpression getDefaultExpression(ExprInfo exprInfo, String pattern) { + private static @NotNull DefaultExpression getDefaultExpression(ExprInfo exprInfo, String pattern) { DefaultExpression expr = exprInfo.classes[0].getDefaultExpression(); if (expr == null) throw new SkriptAPIException("The class '" + exprInfo.classes[0].getCodeName() + "' does not provide a default expression. Either allow null (with %-" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + pattern + "]"); @@ -293,8 +278,7 @@ private T parse(Iterator Variable parseVariable(String expr, Class[] returnTypes) { + private static @Nullable Variable parseVariable(String expr, Class[] returnTypes) { if (VARIABLE_PATTERN.matcher(expr).matches()) { String variableName = "" + expr.substring(expr.indexOf('{') + 1, expr.lastIndexOf('}')); boolean inExpression = false; @@ -317,19 +301,18 @@ private static Variable parseVariable(String expr, Class[] r return null; } - @Nullable - private static Expression parseExpression(Class[] types, String expr) {; + private static @Nullable Expression parseExpression(Class[] types, String expr) {; if (expr.startsWith("\"") && expr.length() != 1 && nextQuote(expr, 1) == expr.length() - 1) { return VariableString.newInstance("" + expr.substring(1, expr.length() - 1)); } else { + //noinspection unchecked,rawtypes return (Expression) parse(expr, (Iterator) Skript.getExpressions(types), null); } } - @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) - private Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, Class... types) { + @SuppressWarnings({"unchecked"}) + private @Nullable Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, Class... types) { assert types.length > 0; assert types.length == 1 || !CollectionUtils.contains(types, Object.class); if (expr.isEmpty()) @@ -339,8 +322,7 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, types); - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { if (context == ParseContext.DEFAULT || context == ParseContext.EVENT) { Variable parsedVariable = parseVariable(expr, types); if (parsedVariable != null) { @@ -358,6 +340,7 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral FunctionReference functionReference = parseFunction(types); if (functionReference != null) { log.printLog(); + //noinspection rawtypes return new ExprFunctionCall(functionReference); } else if (log.hasError()) { log.printError(); @@ -414,13 +397,10 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral } log.printError(); return null; - } finally { - log.stop(); } } - @Nullable - private Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, ExprInfo exprInfo) { + private @Nullable Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, ExprInfo exprInfo) { if (expr.isEmpty()) // Empty expressions return nothing, obviously return null; @@ -430,8 +410,7 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, exprInfo); - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { // Construct types array which contains all potential classes Class[] types = new Class[exprInfo.classes.length]; // This may contain nulls! boolean hasSingular = false; @@ -602,10 +581,18 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo return new SimpleLiteral<>(parsedObject, false, new UnparsedLiteral(expr)); } } + if (expr.startsWith("\"") && expr.endsWith("\"") && expr.length() > 1) { + for (ClassInfo aClass : exprInfo.classes) { + if (!aClass.getC().isAssignableFrom(String.class)) + continue; + VariableString string = VariableString.newInstance(expr.substring(1, expr.length() - 1)); + if (string instanceof LiteralString) + return string; + break; + } + } log.printError(); return null; - } finally { - log.stop(); } } @@ -627,13 +614,12 @@ private SkriptParser suppressMissingAndOrWarnings() { return this; } - @Nullable @SuppressWarnings("unchecked") - public Expression parseExpression(Class... types) { + public @Nullable Expression parseExpression(Class... types) { if (expr.length() == 0) return null; - assert types != null && types.length > 0; + assert types.length > 0; assert types.length == 1 || !CollectionUtils.contains(types, Object.class); ParseLogHandler log = SkriptLogger.startParseLogHandler(); @@ -651,8 +637,7 @@ public Expression parseExpression(Class... types) } } - @Nullable - private Expression parseExpressionList(ParseLogHandler log, Class... types) { + private @Nullable Expression parseExpressionList(ParseLogHandler log, Class... types) { boolean isObject = types.length == 1 && types[0] == Object.class; List> parsedExpressions = new ArrayList<>(); Kleenean and = Kleenean.UNKNOWN; @@ -759,14 +744,12 @@ private Expression parseExpressionList(ParseLogHandler log, Cla } } - @Nullable - public Expression parseExpression(ExprInfo exprInfo) { + public @Nullable Expression parseExpression(ExprInfo exprInfo) { if (expr.length() == 0) return null; boolean isObject = exprInfo.classes.length == 1 && exprInfo.classes[0].getC() == Object.class; - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { // Attempt to parse a single expression Expression parsedExpression = parseSingleExpr(true, null, exprInfo); if (parsedExpression != null) { @@ -890,8 +873,6 @@ public Expression parseExpression(ExprInfo exprInfo) { return new ExpressionList(expressions, Classes.getSuperClassInfo(exprReturnTypes).getC(), exprReturnTypes, !and.isFalse()); } - } finally { - log.stop(); } } @@ -901,14 +882,12 @@ public Expression parseExpression(ExprInfo exprInfo) { * @param types The required return type or null if it is not used (e.g. when calling a void function) * @return The parsed function, or null if the given expression is not a function call or is an invalid function call (check for an error to differentiate these two) */ - @Nullable @SuppressWarnings("unchecked") - public FunctionReference parseFunction(@Nullable Class... types) { + public @Nullable FunctionReference parseFunction(@Nullable Class... types) { if (context != ParseContext.DEFAULT && context != ParseContext.EVENT) return null; - ParseLogHandler log = SkriptLogger.startParseLogHandler(); AtomicBoolean unaryArgument = new AtomicBoolean(false); - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { Matcher matcher = FUNCTION_CALL_PATTERN.matcher(expr); if (!matcher.matches()) { log.printLog(); @@ -963,8 +942,6 @@ public FunctionReference parseFunction(@Nullable Class... ty } log.printLog(); return functionReference; - } finally { - log.stop(); } } @@ -1016,8 +993,7 @@ public static boolean parseArguments(String args, ScriptCommand command, ScriptC *

* Prints parse errors (i.e. must start a ParseLog before calling this method) */ - @Nullable - public static ParseResult parse(String text, String pattern) { + public static @Nullable ParseResult parse(String text, String pattern) { return new SkriptParser(text, PARSE_LITERALS, ParseContext.COMMAND).parse_i(pattern); } @@ -1026,8 +1002,7 @@ public static ParseResult parse(String text, String pattern) { *

* Prints parse errors (i.e. must start a ParseLog before calling this method) */ - @Nullable - public static ParseResult parse(String text, String pattern, int parseFlags, ParseContext parseContext) { + public static @Nullable ParseResult parse(String text, String pattern, int parseFlags, ParseContext parseContext) { return new SkriptParser(text, parseFlags, parseContext).parse_i(pattern); } @@ -1036,8 +1011,7 @@ public static ParseResult parse(String text, String pattern, int parseFlags, Par *

* Prints parse errors (i.e. must start a ParseLog before calling this method) */ - @Nullable - public static ParseResult parse(String text, SkriptPattern pattern, int parseFlags, ParseContext parseContext) { + public static @Nullable ParseResult parse(String text, SkriptPattern pattern, int parseFlags, ParseContext parseContext) { return parse(text, pattern.toString(), parseFlags, parseContext); } @@ -1307,8 +1281,7 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, private static final Map patterns = new ConcurrentHashMap<>(); - @Nullable - private ParseResult parse_i(String pattern) { + private @Nullable ParseResult parse_i(String pattern) { SkriptPattern skriptPattern = patterns.computeIfAbsent(pattern, PatternCompiler::compile); ch.njol.skript.patterns.MatchResult matchResult = skriptPattern.match(expr, flags, context); if (matchResult == null) @@ -1322,8 +1295,7 @@ private ParseResult parse_i(String pattern) { * @param pattern The pattern string to validate * @return The pattern with %codenames% and a boolean array that contains whether the expressions are plural or not */ - @Nullable - public static NonNullPair, Boolean>[]> validatePattern(String pattern) { + public static @Nullable NonNullPair, Boolean>[]> validatePattern(String pattern) { List, Boolean>> pairs = new ArrayList<>(); int groupLevel = 0, optionalLevel = 0; Deque groups = new LinkedList<>(); @@ -1390,8 +1362,7 @@ public static NonNullPair, Boolean>[]> validate return new NonNullPair<>(stringBuilder.toString(), pairs.toArray(new NonNullPair[0])); } - @Nullable - private static NonNullPair, Boolean>[]> error(final String error) { + private static @Nullable NonNullPair, Boolean>[]> error(final String error) { Skript.error("Invalid pattern: " + error); return null; } diff --git a/src/main/java/ch/njol/skript/lang/Statement.java b/src/main/java/ch/njol/skript/lang/Statement.java index 2f4f783541c..b19689f1bc2 100644 --- a/src/main/java/ch/njol/skript/lang/Statement.java +++ b/src/main/java/ch/njol/skript/lang/Statement.java @@ -40,11 +40,8 @@ public abstract class Statement extends TriggerItem implements SyntaxElement { return parse(input, null, defaultError); } - @Nullable - @SuppressWarnings({"rawtypes", "unchecked"}) - public static Statement parse(String input, @Nullable List items, String defaultError) { - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { + public static @Nullable Statement parse(String input, @Nullable List items, String defaultError) { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { EffFunctionCall functionCall = EffFunctionCall.parse(input); if (functionCall != null) { log.printLog(); @@ -62,6 +59,7 @@ public static Statement parse(String input, @Nullable List items, S } log.clear(); + //noinspection unchecked,rawtypes Statement statement = (Statement) SkriptParser.parse(input, (Iterator) Skript.getStatements().iterator(), defaultError); if (statement != null) { log.printLog(); @@ -70,8 +68,6 @@ public static Statement parse(String input, @Nullable List items, S log.printError(); return null; - } finally { - log.stop(); } } diff --git a/src/main/java/ch/njol/skript/lang/Trigger.java b/src/main/java/ch/njol/skript/lang/Trigger.java index b9092703247..d6e18a1f5ee 100644 --- a/src/main/java/ch/njol/skript/lang/Trigger.java +++ b/src/main/java/ch/njol/skript/lang/Trigger.java @@ -18,10 +18,10 @@ */ package ch.njol.skript.lang; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.variables.Variables; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.script.Script; import java.util.List; @@ -30,8 +30,7 @@ public class Trigger extends TriggerSection { private final String name; private final SkriptEvent event; - @Nullable - private final Script script; + private final @Nullable Script script; private int line = -1; // -1 is default: it means there is no line number available private String debugLabel; @@ -70,8 +69,7 @@ public boolean execute(Event event) { } @Override - @Nullable - protected TriggerItem walk(Event event) { + protected @Nullable TriggerItem walk(Event event) { return walk(event, true); } @@ -94,8 +92,7 @@ public SkriptEvent getEvent() { /** * @return The script this trigger was created from. */ - @Nullable - public Script getScript() { + public @Nullable Script getScript() { return script; } diff --git a/src/main/java/ch/njol/skript/lang/TriggerItem.java b/src/main/java/ch/njol/skript/lang/TriggerItem.java index ce4965c64f4..eb192be11f4 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerItem.java +++ b/src/main/java/ch/njol/skript/lang/TriggerItem.java @@ -22,7 +22,7 @@ import ch.njol.skript.util.SkriptColor; import ch.njol.util.StringUtils; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.script.Script; import java.io.File; @@ -37,10 +37,8 @@ */ public abstract class TriggerItem implements Debuggable { - @Nullable - protected TriggerSection parent = null; - @Nullable - private TriggerItem next = null; + protected @Nullable TriggerSection parent = null; + private @Nullable TriggerItem next = null; protected TriggerItem() {} @@ -56,8 +54,7 @@ protected TriggerItem(TriggerSection parent) { * @param event The event * @return The next item to run or null to stop execution */ - @Nullable - protected TriggerItem walk(Event event) { + protected @Nullable TriggerItem walk(Event event) { if (run(event)) { debug(event, true); return next; @@ -118,8 +115,7 @@ public static boolean walk(TriggerItem start, Event event) { */ private final static String INDENT = " "; - @Nullable - private String indentation = null; + private @Nullable String indentation = null; public String getIndentation() { if (indentation == null) { @@ -148,16 +144,14 @@ public TriggerItem setParent(@Nullable TriggerSection parent) { return this; } - @Nullable - public final TriggerSection getParent() { + public final @Nullable TriggerSection getParent() { return parent; } /** * @return The trigger this item belongs to, or null if this is a stand-alone item (e.g. the effect of an effect command) */ - @Nullable - public final Trigger getTrigger() { + public final @Nullable Trigger getTrigger() { TriggerItem triggerItem = this; while (triggerItem != null && !(triggerItem instanceof Trigger)) triggerItem = triggerItem.getParent(); @@ -169,8 +163,7 @@ public TriggerItem setNext(@Nullable TriggerItem next) { return this; } - @Nullable - public TriggerItem getNext() { + public @Nullable TriggerItem getNext() { return next; } diff --git a/src/main/java/ch/njol/skript/lang/TriggerSection.java b/src/main/java/ch/njol/skript/lang/TriggerSection.java index d1343d43605..c861805525a 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerSection.java +++ b/src/main/java/ch/njol/skript/lang/TriggerSection.java @@ -22,8 +22,9 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.parser.ParserInstance; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; /** @@ -31,8 +32,7 @@ */ public abstract class TriggerSection extends TriggerItem { - @Nullable - protected TriggerItem first, last; + protected @Nullable TriggerItem first, last; /** * Reserved for new Trigger(...) @@ -42,12 +42,17 @@ protected TriggerSection(List items) { } protected TriggerSection(SectionNode node) { - List currentSections = ParserInstance.get().getCurrentSections(); - currentSections.add(this); + ParserInstance parser = ParserInstance.get(); + List previousSections = parser.getCurrentSections(); + + List sections = new ArrayList<>(previousSections); + sections.add(this); + parser.setCurrentSections(sections); + try { setTriggerItems(ScriptLoader.loadItems(node)); } finally { - currentSections.remove(currentSections.size() - 1); + parser.setCurrentSections(previousSections); } } @@ -97,11 +102,9 @@ protected final boolean run(Event event) { } @Override - @Nullable - protected abstract TriggerItem walk(Event event); + protected abstract @Nullable TriggerItem walk(Event event); - @Nullable - protected final TriggerItem walk(Event event, boolean run) { + protected final @Nullable TriggerItem walk(Event event, boolean run) { debug(event, run); if (run && first != null) { return first; diff --git a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java index 9afe7bf664d..469ef7aa81d 100644 --- a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java @@ -31,7 +31,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.NonNullIterator; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.logging.Level; @@ -43,8 +43,7 @@ public class UnparsedLiteral implements Literal { private final String data; - @Nullable - private final LogEntry error; + private final @Nullable LogEntry error; /** * @param data non-null, non-empty & trimmed string @@ -76,13 +75,11 @@ public Class getReturnType() { } @Override - @Nullable - public Literal getConvertedExpression(Class... to) { + public @Nullable Literal getConvertedExpression(Class... to) { return getConvertedExpression(ParseContext.DEFAULT, to); } - @Nullable - public Literal getConvertedExpression(ParseContext context, Class... to) { + public @Nullable Literal getConvertedExpression(ParseContext context, Class... to) { assert to.length > 0; assert to.length == 1 || !CollectionUtils.contains(to, Object.class); ParseLogHandler log = SkriptLogger.startParseLogHandler(); @@ -178,7 +175,7 @@ public NonNullIterator iterator(Event event) { } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) throws UnsupportedOperationException { throw invalidAccessException(); } diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 160276e11d3..a9cfa472000 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -18,16 +18,6 @@ */ package ch.njol.skript.lang; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.TreeMap; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; @@ -35,9 +25,6 @@ import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.arithmetic.Arithmetics; -import org.skriptlang.skript.lang.arithmetic.OperationInfo; -import org.skriptlang.skript.lang.arithmetic.Operator; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; @@ -58,13 +45,26 @@ import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.OperationInfo; +import org.skriptlang.skript.lang.arithmetic.Operator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptWarning; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.TreeMap; + public class Variable implements Expression { private final static String SINGLE_SEPARATOR_CHAR = ":"; @@ -74,8 +74,7 @@ public class Variable implements Expression { /** * Script this variable was created in. */ - @Nullable - private final Script script; + private final @Nullable Script script; /** * The name of this variable, excluding the local variable token, but including the list variable token '::*'. @@ -88,8 +87,7 @@ public class Variable implements Expression { private final boolean local; private final boolean list; - @Nullable - private final Variable source; + private final @Nullable Variable source; @SuppressWarnings("unchecked") private Variable(VariableString name, Class[] types, boolean local, boolean list, @Nullable Variable source) { @@ -173,8 +171,7 @@ else if (character == '%') /** * Prints errors */ - @Nullable - public static Variable newInstance(String name, Class[] types) { + public static @Nullable Variable newInstance(String name, Class[] types) { name = "" + name.trim(); if (!isValidVariableName(name, true, true)) return null; @@ -301,8 +298,7 @@ public Variable getConvertedExpression(Class... to) { * Gets the value of this variable as stored in the variables map. * This method also checks against default variables. */ - @Nullable - public Object getRaw(Event event) { + public @Nullable Object getRaw(Event event) { DefaultVariables data = script == null ? null : script.getData(DefaultVariables.class); if (data != null) data.enterScope(); @@ -332,9 +328,7 @@ public Object getRaw(Event event) { return null; } - @Nullable - @SuppressWarnings("unchecked") - private Object get(Event event) { + private @Nullable Object get(Event event) { Object rawValue = getRaw(event); if (!list) return rawValue; @@ -342,10 +336,12 @@ private Object get(Event event) { return Array.newInstance(types[0], 0); List convertedValues = new ArrayList<>(); String name = StringUtils.substring(this.name.toString(event), 0, -1); + //noinspection unchecked for (Entry variable : ((Map) rawValue).entrySet()) { if (variable.getKey() != null && variable.getValue() != null) { Object value; if (variable.getValue() instanceof Map) + //noinspection unchecked value = ((Map) variable.getValue()).get(null); else value = variable.getValue(); @@ -361,8 +357,7 @@ private Object get(Event event) { * because the player object inside the variable will be a (kinda) dead variable * as a new player object has been created by the server. */ - @Nullable - Object convertIfOldPlayer(String key, Event event, @Nullable Object object) { + @Nullable Object convertIfOldPlayer(String key, Event event, @Nullable Object object) { if (SkriptConfig.enablePlayerVariableFix.value() && object instanceof Player) { Player oldPlayer = (Player) object; if (!oldPlayer.isValid() && oldPlayer.isOnline()) { @@ -385,11 +380,9 @@ public Iterator> variablesIterator(Event event) { // temporary list to prevent CMEs @SuppressWarnings("unchecked") Iterator keys = new ArrayList<>(((Map) val).keySet()).iterator(); - return new Iterator>() { - @Nullable - private String key; - @Nullable - private Object next = null; + return new Iterator<>() { + private @Nullable String key; + private @Nullable Object next = null; @Override public boolean hasNext() { @@ -424,9 +417,7 @@ public void remove() { } @Override - @Nullable - @SuppressWarnings("unchecked") - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { if (!list) { T value = getSingle(event); return value != null ? new SingleItemIterator<>(value) : null; @@ -437,22 +428,20 @@ public Iterator iterator(Event event) { return new EmptyIterator<>(); assert value instanceof TreeMap; // temporary list to prevent CMEs + //noinspection unchecked Iterator keys = new ArrayList<>(((Map) value).keySet()).iterator(); - return new Iterator() { - @Nullable - private String key; - @Nullable - private T next = null; + return new Iterator<>() { + private @Nullable T next = null; @Override - @SuppressWarnings({"unchecked"}) public boolean hasNext() { if (next != null) return true; while (keys.hasNext()) { - key = keys.next(); + @Nullable String key = keys.next(); if (key != null) { next = Converters.convert(Variables.getVariable(name + key, event, local), types); + //noinspection unchecked next = (T) convertIfOldPlayer(name + key, event, next); if (next != null && !(next instanceof TreeMap)) return true; @@ -479,8 +468,7 @@ public void remove() { }; } - @Nullable - private T getConverted(Event event) { + private @Nullable T getConverted(Event event) { assert !list; return Converters.convert(get(event), types); } @@ -502,7 +490,7 @@ private void setIndex(Event event, String index, @Nullable Object value) { } @Override - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { if (!list && mode == ChangeMode.SET) return CollectionUtils.array(Object.class); return CollectionUtils.array(Object[].class); @@ -510,7 +498,7 @@ public Class[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) throws UnsupportedOperationException { switch (mode) { case DELETE: if (list) { @@ -667,8 +655,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throw } @Override - @Nullable - public T getSingle(Event event) { + public @Nullable T getSingle(Event event) { if (list) throw new SkriptAPIException("Invalid call to getSingle"); return getConverted(event); diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index a81cd600a4e..a44694cc6d3 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -19,7 +19,6 @@ package ch.njol.skript.lang; import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.expressions.ExprColoured; @@ -43,9 +42,8 @@ import ch.njol.util.coll.iterator.SingleItemIterator; import com.google.common.collect.Lists; import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.lang.script.Script; @@ -60,19 +58,17 @@ */ public class VariableString implements Expression { - @Nullable - private final Script script; - private final String orig; + private final @Nullable Script script; + protected final String original; - @Nullable - private final Object[] strings; - @Nullable - private Object[] stringsUnformatted; + private final Object @Nullable [] strings; + + + private Object @Nullable [] stringsUnformatted; private final boolean isSimple; - @Nullable - private final String simple, simpleUnformatted; + private final @Nullable String simple, simpleUnformatted; private final StringMode mode; /** @@ -83,15 +79,15 @@ public class VariableString implements Expression { /** * Creates a new VariableString which does not contain variables. - * + * * @param input Content for string. */ - private VariableString(String input) { + protected VariableString(String input) { this.isSimple = true; this.simpleUnformatted = input.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! this.simple = Utils.replaceChatStyles(simpleUnformatted); - this.orig = simple; + this.original = simple; this.strings = null; this.mode = StringMode.MESSAGE; @@ -103,13 +99,13 @@ private VariableString(String input) { /** * Creates a new VariableString which contains variables. - * + * * @param original Original string (unparsed). * @param strings Objects, some of them are variables. * @param mode String mode. */ private VariableString(String original, Object[] strings, StringMode mode) { - this.orig = original; + this.original = original; this.strings = new Object[strings.length]; this.stringsUnformatted = new Object[strings.length]; @@ -143,20 +139,18 @@ private VariableString(String original, Object[] strings, StringMode mode) { /** * Prints errors */ - @Nullable - public static VariableString newInstance(String input) { + public static @Nullable VariableString newInstance(String input) { return newInstance(input, StringMode.MESSAGE); } /** * Creates an instance of VariableString by parsing given string. * Prints errors and returns null if it is somehow invalid. - * + * * @param original Unquoted string to parse. * @return A new VariableString instance. */ - @Nullable - public static VariableString newInstance(String original, StringMode mode) { + public static @Nullable VariableString newInstance(String original, StringMode mode) { if (mode != StringMode.VARIABLE_NAME && !isQuotedCorrectly(original, false)) return null; @@ -251,7 +245,7 @@ public static VariableString newInstance(String original, StringMode mode) { // Check if this isn't actually variable string, and return if (strings.size() == 1 && strings.get(0) instanceof String) - return new VariableString(original); + return new LiteralString(original); if (strings.size() == 1 && strings.get(0) instanceof Expression && ((Expression) strings.get(0)).getReturnType() == String.class && @@ -285,7 +279,7 @@ public static String quote(String string) { /** * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it. * Singular double quotes are only allowed between percentage signs. - * + * * @param string The string to test * @param withQuotes Whether the string must be surrounded by double quotes or not * @return Whether the string is quoted correctly @@ -316,7 +310,7 @@ public static boolean isQuotedCorrectly(String string, boolean withQuotes) { /** * Removes quoted quotes from a string. - * + * * @param string The string to remove quotes from * @param surroundingQuotes Whether the string has quotes at the start & end that should be removed * @return The string with double quotes replaced with single ones and optionally with removed surrounding quotes. @@ -330,7 +324,7 @@ public static String unquote(String string, boolean surroundingQuotes) { /** * Copied from {@code SkriptParser#nextBracket(String, char, char, int, boolean)}, but removed escaping & returns -1 on error. - * + * * @param string The string to search in * @param start Index after the opening bracket * @return The next closing curly bracket @@ -366,8 +360,7 @@ public static VariableString[] makeStrings(String[] args) { * @param args Quoted strings - This is not checked! * @return a new array containing all newly created VariableStrings, or null if one is invalid */ - @Nullable - public static VariableString[] makeStringsFromQuoted(List args) { + public static VariableString @Nullable [] makeStringsFromQuoted(List args) { VariableString[] strings = new VariableString[args.size()]; for (int i = 0; i < args.size(); i++) { assert args.get(i).startsWith("\"") && args.get(i).endsWith("\""); @@ -485,7 +478,7 @@ public List getMessageComponentsUnsafe(Event event) { /** * Parses all expressions in the string and returns it in chat JSON format. - * + * * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. */ @@ -493,8 +486,7 @@ public String toChatString(Event event) { return ChatMessages.toJson(getMessageComponents(event)); } - @Nullable - private static ChatColor getLastColor(CharSequence sequence) { + private static @Nullable ChatColor getLastColor(CharSequence sequence) { for (int i = sequence.length() - 2; i >= 0; i--) { if (sequence.charAt(i) == ChatColor.COLOR_CHAR) { ChatColor color = ChatColor.getByChar(sequence.charAt(i + 1)); @@ -513,7 +505,7 @@ public String toString() { /** * Parses all expressions in the string and returns it. * If this is a simple string, the event may be null. - * + * * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. */ @@ -573,11 +565,10 @@ public String toString(@Nullable Event event, boolean debug) { /** * Builds all possible default variable type hints based on the super type of the expression. - * + * * @return List of all possible super class code names. */ - @NotNull - public List getDefaultVariableNames(String variableName, Event event) { + public @NotNull List getDefaultVariableNames(String variableName, Event event) { if (script == null || mode != StringMode.VARIABLE_NAME) return Lists.newArrayList(); @@ -629,7 +620,7 @@ public VariableString setMode(StringMode mode) { if (this.mode == mode || isSimple) return this; try (BlockingLogHandler ignored = new BlockingLogHandler().start()) { - VariableString variableString = newInstance(orig, mode); + VariableString variableString = newInstance(original, mode); if (variableString == null) { assert false : this + "; " + mode; return this; @@ -674,9 +665,8 @@ public boolean check(Event event, Checker checker) { } @Override - @Nullable @SuppressWarnings("unchecked") - public Expression getConvertedExpression(Class... to) { + public @Nullable Expression getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, String.class)) return (Expression) this; return ConvertedExpression.newInstance(this, to); @@ -688,13 +678,12 @@ public Class getReturnType() { } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { return null; } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @@ -733,7 +722,6 @@ public Expression getSource() { return this; } - @SuppressWarnings("unchecked") public static Expression setStringMode(Expression expression, StringMode mode) { if (expression instanceof ExpressionList) { Expression[] expressions = ((ExpressionList) expression).getExpressions(); @@ -743,6 +731,7 @@ public static Expression setStringMode(Expression expression, StringMo expressions[i] = setStringMode(expr, mode); } } else if (expression instanceof VariableString) { + //noinspection unchecked return (Expression) ((VariableString) expression).setMode(mode); } return expression; diff --git a/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java index adec5001679..e9098a70cef 100644 --- a/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java @@ -48,14 +48,14 @@ public static EffFunctionCall parse(final String line) { } @Override - protected void execute(final Event e) { - function.execute(e); + protected void execute(final Event event) { + function.execute(event); function.resetReturnValue(); // Function might have return value that we're ignoring } @Override - public String toString(@Nullable final Event e, final boolean debug) { - return function.toString(e, debug); + public String toString(@Nullable final Event event, final boolean debug) { + return function.toString(event, debug); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index 09d9966c586..1352788ecfe 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -21,12 +21,12 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; public class ExprFunctionCall extends SimpleExpression { @@ -55,17 +55,15 @@ public ExprFunctionCall(FunctionReference function, Class[] expe } @Override - @Nullable - protected T[] get(Event e) { - Object[] returnValue = function.execute(e); + protected T @Nullable [] get(Event event) { + Object[] returnValue = function.execute(event); function.resetReturnValue(); return Converters.convert(returnValue, returnTypes, returnType); } @Override - @Nullable @SuppressWarnings("unchecked") - public Expression getConvertedExpression(Class... to) { + public @Nullable Expression getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, getReturnType())) return (Expression) this; assert function.getReturnType() != null; @@ -86,8 +84,8 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return function.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return function.toString(event, debug); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Function.java b/src/main/java/ch/njol/skript/lang/function/Function.java index 166191f5f21..b358844dea4 100644 --- a/src/main/java/ch/njol/skript/lang/function/Function.java +++ b/src/main/java/ch/njol/skript/lang/function/Function.java @@ -18,14 +18,13 @@ */ package ch.njol.skript.lang.function; -import java.util.Arrays; - -import org.bukkit.Bukkit; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; /** * Functions can be called using arguments. @@ -68,9 +67,8 @@ public Parameter getParameter(int index) { public boolean isSingle() { return sign.isSingle(); } - - @Nullable - public ClassInfo getReturnType() { + + public @Nullable ClassInfo getReturnType() { return sign.getReturnType(); } @@ -83,9 +81,7 @@ public ClassInfo getReturnType() { * {@link Signature#getMaxParameters()} elements. * @return The result(s) of this function */ - @SuppressWarnings("null") - @Nullable - public final T[] execute(Object[][] params) { + public final T @Nullable [] execute(Object[][] params) { FunctionEvent e = new FunctionEvent<>(this); // Call function event only if requested by addon @@ -142,14 +138,13 @@ public final T[] execute(Object[][] params) { * Executes this function with given parameters. Usually, using * {@link #execute(Object[][])} is better; it handles optional arguments * and function event creation automatically. - * @param e Associated function event. This is usually created by Skript. + * @param event Associated function event. This is usually created by Skript. * @param params Function parameters. * There must be {@link Signature#getMaxParameters()} amount of them, and * you need to manually handle default values. * @return Function return value(s). */ - @Nullable - public abstract T[] execute(FunctionEvent e, Object[][] params); + public abstract T @Nullable [] execute(FunctionEvent event, Object[][] params); /** * Resets the return value of the {@code Function}. diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 409e4edd686..f60f480ad8c 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -28,12 +28,12 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.converter.Converters; +import ch.njol.skript.util.Contract; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.StringUtils; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.util.Contract; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; import java.util.ArrayList; import java.util.Arrays; @@ -53,15 +53,13 @@ public class FunctionReference implements Contract { * Signature of referenced function. If {@link #validateFunction(boolean)} * succeeds, this is not null. */ - @Nullable - private Signature signature; + private @Nullable Signature signature; /** * Actual function reference. Null before the function is called for first * time. */ - @Nullable - private Function function; + private @Nullable Function function; /** * If all function parameters can be condensed to a single list. @@ -89,15 +87,13 @@ public class FunctionReference implements Contract { /** * Node for {@link #validateFunction(boolean)} to use for logging. */ - @Nullable - private final Node node; + private final @Nullable Node node; /** * Script in which this reference is found. Used for function unload * safety checks. */ - @Nullable - public final String script; + public final @Nullable String script; /** * The contract for this function (typically the function reference itself). @@ -133,7 +129,6 @@ public boolean validateParameterArity(boolean first) { * this is called when the function signature changes. * @return True if validation succeeded. */ - @SuppressWarnings("unchecked") public boolean validateFunction(boolean first) { if (!first && script == null) return false; @@ -230,6 +225,7 @@ public boolean validateFunction(boolean first) { Parameter p = sign.parameters[singleListParam ? 0 : i]; RetainingLogHandler log = SkriptLogger.startRetainingLog(); try { + //noinspection unchecked Expression e = parameters[i].getConvertedExpression(p.type.getC()); if (e == null) { if (first) { @@ -262,7 +258,8 @@ public boolean validateFunction(boolean first) { log.printLog(); } } - + + //noinspection unchecked signature = (Signature) sign; sign.calls.add(this); @@ -273,8 +270,7 @@ public boolean validateFunction(boolean first) { return true; } - @Nullable - public Function getFunction() { + public @Nullable Function getFunction() { return function; } @@ -284,11 +280,10 @@ public boolean resetReturnValue() { return false; } - @SuppressWarnings("unchecked") - @Nullable - protected T[] execute(Event e) { + protected T @Nullable [] execute(Event event) { // If needed, acquire the function reference if (function == null) + //noinspection unchecked function = (Function) Functions.getFunction(functionName, script); if (function == null) { // It might be impossible to resolve functions in some cases! @@ -301,7 +296,7 @@ protected T[] execute(Event e) { if (singleListParam && parameters.length > 1) { // All parameters to one list List l = new ArrayList<>(); for (Expression parameter : parameters) - l.addAll(Arrays.asList(parameter.getArray(e))); + l.addAll(Arrays.asList(parameter.getArray(event))); params[0] = l.toArray(); // Don't allow mutating across function boundary; same hack is applied to variables @@ -310,7 +305,7 @@ protected T[] execute(Event e) { } } else { // Use parameters in normal way for (int i = 0; i < parameters.length; i++) { - Object[] array = parameters[i].getArray(e); + Object[] array = parameters[i].getArray(event); params[i] = Arrays.copyOf(array, array.length); // Don't allow mutating across function boundary; same hack is applied to variables for (int j = 0; j < params[i].length; j++) { @@ -332,19 +327,16 @@ public boolean isSingle(Expression... arguments) { return single; } - @Nullable - public Class getReturnType() { + public @Nullable Class getReturnType() { //noinspection unchecked return (Class) contract.getReturnType(parameters); } @Override - @Nullable - public Class getReturnType(Expression... arguments) { + public @Nullable Class getReturnType(Expression... arguments) { if (signature == null) throw new SkriptAPIException("Signature of function is null when return type is asked!"); - @SuppressWarnings("ConstantConditions") ClassInfo ret = signature.returnType; return ret == null ? null : ret.getC(); } @@ -357,12 +349,12 @@ public Contract getContract() { return contract; } - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { StringBuilder b = new StringBuilder(functionName + "("); for (int i = 0; i < parameters.length; i++) { if (i != 0) b.append(", "); - b.append(parameters[i].toString(e, debug)); + b.append(parameters[i].toString(event, debug)); } b.append(")"); return b.toString(); diff --git a/src/main/java/ch/njol/skript/lang/function/Functions.java b/src/main/java/ch/njol/skript/lang/function/Functions.java index 2eee7ea6c18..71bbd568d5d 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -23,14 +23,11 @@ import ch.njol.skript.SkriptAddon; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.SectionNode; -import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.script.Script; import java.util.ArrayList; @@ -53,8 +50,7 @@ public abstract class Functions { private Functions() {} - @Nullable - public static ScriptFunction currentFunction = null; + public static @Nullable ScriptFunction currentFunction = null; /** * Function namespaces. @@ -105,8 +101,7 @@ public static JavaFunction registerFunction(JavaFunction function) { * to get a new signature instance and {@link Functions#registerSignature(Signature)} to register the signature * @return Script function, or null if something went wrong. */ - @Nullable - public static Function loadFunction(Script script, SectionNode node, Signature signature) { + public static @Nullable Function loadFunction(Script script, SectionNode node, Signature signature) { String name = signature.name; Namespace namespace = getScriptNamespace(script.getConfig().getFileName()); if (namespace == null) { @@ -140,9 +135,7 @@ public static Function loadFunction(Script script, SectionNode node, Signatur * @return Parsed signature or null if something went wrong. * @see Functions#registerSignature(Signature) */ - @Nullable - @SuppressWarnings({"unchecked", "null"}) - public static Signature parseSignature(String script, String name, String args, @Nullable String returnType, boolean local) { + public static @Nullable Signature parseSignature(String script, String name, String args, @Nullable String returnType, boolean local) { List> parameters = Parameter.parse(args); if (parameters == null) return null; @@ -162,6 +155,7 @@ public static Signature parseSignature(String script, String name, String arg if (returnClass == null) return signError("Cannot recognise the type '" + returnType + "'"); } + //noinspection unchecked return new Signature<>(script, name, parameters.toArray(new Parameter[0]), local, (ClassInfo) returnClass, singleReturn, null); } @@ -171,8 +165,7 @@ public static Signature parseSignature(String script, String name, String arg * @return Signature of function, or null if something went wrong. * @see Functions#parseSignature(String, String, String, String, boolean) */ - @Nullable - public static Signature registerSignature(Signature signature) { + public static @Nullable Signature registerSignature(Signature signature) { // Ensure there are no duplicate functions if (signature.local) { Namespace namespace = getScriptNamespace(signature.script); @@ -207,8 +200,7 @@ public static Signature registerSignature(Signature signature) { * @param error Error message. * @return Null. */ - @Nullable - private static Function error(String error) { + private static @Nullable Function error(String error) { Skript.error(error); return null; } @@ -218,8 +210,7 @@ private static Function error(String error) { * @param error Error message. * @return Null. */ - @Nullable - private static Signature signError(String error) { + private static @Nullable Signature signError(String error) { Skript.error(error); return null; } @@ -234,8 +225,7 @@ private static Signature signError(String error) { * @return Function, or null if it does not exist. */ @Deprecated - @Nullable - public static Function getFunction(String name) { + public static @Nullable Function getFunction(String name) { return getGlobalFunction(name); } @@ -247,8 +237,7 @@ public static Function getFunction(String name) { * @param name Name of function. * @return Function, or null if it does not exist. */ - @Nullable - public static Function getGlobalFunction(String name) { + public static @Nullable Function getGlobalFunction(String name) { Namespace namespace = globalFunctions.get(name); if (namespace == null) return null; @@ -264,8 +253,7 @@ public static Function getGlobalFunction(String name) { * @param script The script where the function is declared in. Used to get local functions. * @return Function, or null if it does not exist. */ - @Nullable - public static Function getLocalFunction(String name, String script) { + public static @Nullable Function getLocalFunction(String name, String script) { Namespace namespace = null; Function function = null; namespace = getScriptNamespace(script); @@ -284,8 +272,7 @@ public static Function getLocalFunction(String name, String script) { * @param script The script where the function is declared in. Used to get local functions. * @return Function, or null if it does not exist. */ - @Nullable - public static Function getFunction(String name, @Nullable String script) { + public static @Nullable Function getFunction(String name, @Nullable String script) { if (script == null) return getGlobalFunction(name); Function function = getLocalFunction(name, script); @@ -302,8 +289,7 @@ public static Function getFunction(String name, @Nullable String script) { * @return Signature, or null if function does not exist. */ @Deprecated - @Nullable - public static Signature getSignature(String name) { + public static @Nullable Signature getSignature(String name) { return getGlobalSignature(name); } @@ -313,8 +299,7 @@ public static Signature getSignature(String name) { * @param name Name of function. * @return Signature, or null if function does not exist. */ - @Nullable - public static Signature getGlobalSignature(String name) { + public static @Nullable Signature getGlobalSignature(String name) { Namespace namespace = globalFunctions.get(name); if (namespace == null) return null; @@ -328,8 +313,7 @@ public static Signature getGlobalSignature(String name) { * @param script The script where the function is declared in. Used to get local functions. * @return Signature, or null if function does not exist. */ - @Nullable - public static Signature getLocalSignature(String name, String script) { + public static @Nullable Signature getLocalSignature(String name, String script) { Namespace namespace = null; Signature signature = null; namespace = getScriptNamespace(script); @@ -346,8 +330,7 @@ public static Signature getLocalSignature(String name, String script) { * @param script The script where the function is declared in. Used to get local functions. * @return Signature, or null if function does not exist. */ - @Nullable - public static Signature getSignature(String name, @Nullable String script) { + public static @Nullable Signature getSignature(String name, @Nullable String script) { if (script == null) return getGlobalSignature(name); Signature signature = getLocalSignature(name, script); @@ -356,8 +339,7 @@ public static Signature getSignature(String name, @Nullable String script) { return signature; } - @Nullable - public static Namespace getScriptNamespace(String script) { + public static @Nullable Namespace getScriptNamespace(String script) { return namespaces.get(new Namespace.Key(Namespace.Origin.SCRIPT, script)); } diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index 5b23f304d77..6806a5b97c8 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -18,10 +18,9 @@ */ package ch.njol.skript.lang.function; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.util.Contract; +import org.jetbrains.annotations.Nullable; /** * @author Peter Güttinger @@ -41,17 +40,12 @@ public JavaFunction(String name, Parameter[] parameters, ClassInfo returnT } @Override - @Nullable - public abstract T[] execute(FunctionEvent e, Object[][] params); - - @Nullable - private String[] description = null; - @Nullable - private String[] examples = null; - @Nullable - private String[] keywords; - @Nullable - private String since = null; + public abstract T @Nullable [] execute(FunctionEvent event, Object[][] params); + + private String @Nullable [] description = null; + private String @Nullable [] examples = null; + private String @Nullable [] keywords; + private @Nullable String since = null; /** * Only used for Skript's documentation. @@ -97,24 +91,20 @@ public JavaFunction since(final String since) { this.since = since; return this; } - - @Nullable - public String[] getDescription() { + + public String @Nullable [] getDescription() { return description; } - - @Nullable - public String[] getExamples() { + + public String @Nullable [] getExamples() { return examples; } - @Nullable - public String[] getKeywords() { + public String @Nullable [] getKeywords() { return keywords; } - - @Nullable - public String getSince() { + + public @Nullable String getSince() { return since; } diff --git a/src/main/java/ch/njol/skript/lang/function/Namespace.java b/src/main/java/ch/njol/skript/lang/function/Namespace.java index 7834e7f8899..be649efd1b0 100644 --- a/src/main/java/ch/njol/skript/lang/function/Namespace.java +++ b/src/main/java/ch/njol/skript/lang/function/Namespace.java @@ -18,13 +18,13 @@ */ package ch.njol.skript.lang.function; +import org.jetbrains.annotations.Nullable; + import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.eclipse.jdt.annotation.Nullable; - /** * Contains a set of functions. */ @@ -52,8 +52,7 @@ public static class Key { private final Origin origin; - @Nullable - private final String scriptName; + private final @Nullable String scriptName; public Key(Origin origin, @Nullable String scriptName) { super(); @@ -65,8 +64,7 @@ public Origin getOrigin() { return origin; } - @Nullable - public String getScriptName() { + public @Nullable String getScriptName() { return scriptName; } @@ -156,14 +154,12 @@ public Namespace() { this.signatures = new HashMap<>(); this.functions = new HashMap<>(); } - - @Nullable - public Signature getSignature(String name, boolean local) { + + public @Nullable Signature getSignature(String name, boolean local) { return signatures.get(new Info(name, local)); } - @Nullable - public Signature getSignature(String name) { + public @Nullable Signature getSignature(String name) { Signature signature = getSignature(name, true); return signature == null ? getSignature(name, false) : signature; } @@ -187,14 +183,12 @@ public boolean removeSignature(Signature sign) { public Collection> getSignatures() { return signatures.values(); } - - @Nullable - public Function getFunction(String name, boolean local) { + + public @Nullable Function getFunction(String name, boolean local) { return functions.get(new Info(name, local)); } - @Nullable - public Function getFunction(String name) { + public @Nullable Function getFunction(String name) { Function function = getFunction(name, true); return function == null ? getFunction(name, false) : function; } diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index b77a95b8c5d..deee1d1593e 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -32,7 +32,7 @@ import ch.njol.skript.util.Utils; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -61,8 +61,7 @@ public final class Parameter { * Expression that will provide default value of this parameter * when the function is called. */ - @Nullable - final Expression def; + final @Nullable Expression def; /** * Whether this parameter takes one or many values. @@ -84,10 +83,8 @@ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expre public ClassInfo getType() { return type; } - - @SuppressWarnings("unchecked") - @Nullable - public static Parameter newInstance(String name, ClassInfo type, boolean single, @Nullable String def) { + + public static @Nullable Parameter newInstance(String name, ClassInfo type, boolean single, @Nullable String def) { if (!Variable.isValidVariableName(name, true, false)) { Skript.error("A parameter's name must be a valid variable name."); // ... because it will be made available as local variable @@ -99,6 +96,7 @@ public static Parameter newInstance(String name, ClassInfo type, boole // Parse the default value expression try { + //noinspection unchecked d = new SkriptParser(def, SkriptParser.ALL_FLAGS, ParseContext.DEFAULT).parseExpression(type.getC()); if (d == null || LiteralUtils.hasUnparsedLiteral(d)) { log.printErrors("Can't understand this expression: " + def); @@ -118,8 +116,7 @@ public static Parameter newInstance(String name, ClassInfo type, boole * @param args The string to parse. * @return The parsed parameters */ - @Nullable - public static List> parse(String args) { + public static @Nullable List> parse(String args) { List> params = new ArrayList<>(); boolean caseInsensitive = SkriptConfig.caseInsensitiveVariables.value(); int j = 0; @@ -188,8 +185,7 @@ public String getName() { * Get the Expression that will be used to provide the default value of this parameter when the function is called. * @return Expression that will provide default value of this parameter */ - @Nullable - public Expression getDefaultExpression() { + public @Nullable Expression getDefaultExpression() { return def; } diff --git a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java index d3a1e85117c..a864537cb00 100644 --- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java @@ -19,16 +19,16 @@ package ch.njol.skript.lang.function; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.lang.ReturnHandler; -import org.jetbrains.annotations.ApiStatus; -import org.skriptlang.skript.lang.script.Script; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.config.SectionNode; -import ch.njol.skript.effects.EffReturn; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ReturnHandler; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.util.SimpleEvent; import ch.njol.skript.variables.Variables; +import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.script.Script; public class ScriptFunction extends Function implements ReturnHandler { @@ -60,33 +60,34 @@ public ScriptFunction(Signature sign, SectionNode node) { // REMIND track possible types of local variables (including undefined variables) (consider functions, commands, and EffChange) - maybe make a general interface for this purpose // REM: use patterns, e.g. {_a%b%} is like "a.*", and thus subsequent {_axyz} may be set and of that type. @Override - public T @Nullable [] execute(final FunctionEvent e, final Object[][] params) { + public T @Nullable [] execute(FunctionEvent event, Object[][] params) { Parameter[] parameters = getSignature().getParameters(); for (int i = 0; i < parameters.length; i++) { - Parameter p = parameters[i]; + Parameter parameter = parameters[i]; Object[] val = params[i]; - if (p.single && val.length > 0) { - Variables.setVariable(p.name, val[0], e, true); + if (parameter.single && val.length > 0) { + Variables.setVariable(parameter.name, val[0], event, true); } else { for (int j = 0; j < val.length; j++) { - Variables.setVariable(p.name + "::" + (j + 1), val[j], e, true); + Variables.setVariable(parameter.name + "::" + (j + 1), val[j], event, true); } } } - trigger.execute(e); + trigger.execute(event); ClassInfo returnType = getReturnType(); return returnType != null ? returnValues : null; } /** - * Should only be called by {@link EffReturn}. - * @deprecated Use {@link ScriptFunction#returnValues(Object[])} + * @deprecated Use {@link ScriptFunction#returnValues(Event, Expression)} */ @Deprecated @ApiStatus.Internal public final void setReturnValue(@Nullable T[] values) { - returnValues(values); + assert !returnValueSet; + returnValueSet = true; + this.returnValues = values; } @Override @@ -97,10 +98,10 @@ public boolean resetReturnValue() { } @Override - public final void returnValues(T @Nullable [] values) { + public final void returnValues(Event event, Expression value) { assert !returnValueSet; returnValueSet = true; - this.returnValues = values; + this.returnValues = value.getArray(event); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index 3b30e2273de..e7e8477d633 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -19,8 +19,8 @@ package ch.njol.skript.lang.function; import ch.njol.skript.classes.ClassInfo; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.util.Contract; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; @@ -56,8 +56,7 @@ public class Signature { * is null. void is never used as return type, because it is not registered * to Skript's type system. */ - @Nullable - final ClassInfo returnType; + final @Nullable ClassInfo returnType; /** * Whether this function returns a single value, or multiple ones. @@ -73,14 +72,12 @@ public class Signature { /** * The class path for the origin of this signature. */ - @Nullable - final String originClassPath; + final @Nullable String originClassPath; /** * An overriding contract for this function (e.g. to base its return on its arguments). */ - @Nullable - final Contract contract; + final @Nullable Contract contract; public Signature(String script, String name, @@ -131,8 +128,7 @@ public boolean isLocal() { return local; } - @Nullable - public ClassInfo getReturnType() { + public @Nullable ClassInfo getReturnType() { return returnType; } @@ -144,8 +140,7 @@ public String getOriginClassPath() { return originClassPath; } - @Nullable - public Contract getContract() { + public @Nullable Contract getContract() { return contract; } diff --git a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java index 87e3bd4f26a..b38c169fa81 100644 --- a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java @@ -18,10 +18,10 @@ */ package ch.njol.skript.lang.function; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.util.Contract; +import org.jetbrains.annotations.Nullable; /** * A {@link JavaFunction} which doesn't make use of @@ -41,19 +41,16 @@ public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo r public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single, Contract contract) { super(name, parameters, returnType, single, contract); } - - @SuppressWarnings("ConstantConditions") - @Nullable + @Override - public final T[] execute(FunctionEvent e, Object[][] params) { + public final T @Nullable [] execute(FunctionEvent event, Object[][] params) { for (Object[] param : params) { if (param == null || param.length == 0 || param[0] == null) return null; } return executeSimple(params); } - - @Nullable - public abstract T[] executeSimple(Object[][] params); + + public abstract T @Nullable [] executeSimple(Object[][] params); } diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 8ed8cd5a919..5e1300d974a 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -112,8 +112,7 @@ public void reset() { // Script API - @Nullable - private Script currentScript = null; + private @Nullable Script currentScript = null; /** * Internal method for updating the current script. Allows null parameter. @@ -151,8 +150,7 @@ public Script getCurrentScript() { // Structure API - @Nullable - private Structure currentStructure = null; + private @Nullable Structure currentStructure = null; /** * Updates the Structure currently being handled by this ParserInstance. @@ -165,8 +163,7 @@ public void setCurrentStructure(@Nullable Structure structure) { /** * @return The Structure currently being handled by this ParserInstance. */ - @Nullable - public Structure getCurrentStructure() { + public @Nullable Structure getCurrentStructure() { return currentStructure; } @@ -191,8 +188,7 @@ public final boolean isCurrentStructure(Class... structureC // Event API - @Nullable - private String currentEventName; + private @Nullable String currentEventName; private Class @Nullable [] currentEvents = null; @@ -200,8 +196,7 @@ public void setCurrentEventName(@Nullable String currentEventName) { this.currentEventName = currentEventName; } - @Nullable - public String getCurrentEventName() { + public @Nullable String getCurrentEventName() { return currentEventName; } @@ -298,12 +293,11 @@ public List getCurrentSections() { * Returns {@code null} if {@link #isCurrentSection(Class)} returns {@code false}. * @see #getCurrentSections() */ - @Nullable - @SuppressWarnings("unchecked") - public T getCurrentSection(Class sectionClass) { + public @Nullable T getCurrentSection(Class sectionClass) { for (int i = currentSections.size(); i-- > 0;) { TriggerSection triggerSection = currentSections.get(i); if (sectionClass.isInstance(triggerSection)) + //noinspection unchecked return (T) triggerSection; } return null; @@ -314,12 +308,11 @@ public T getCurrentSection(Class sectionClass) { * Modifications to the returned list are not saved. * @see #getCurrentSections() */ - @NotNull - @SuppressWarnings("unchecked") - public List getCurrentSections(Class sectionClass) { + public @NotNull List getCurrentSections(Class sectionClass) { List list = new ArrayList<>(); for (TriggerSection triggerSection : currentSections) { if (sectionClass.isInstance(triggerSection)) + //noinspection unchecked list.add((T) triggerSection); } return list; @@ -388,8 +381,7 @@ public HandlerList getHandlers() { return handlers; } - @Nullable - private Node node; + private @Nullable Node node; /** * @param node The node to mark as being handled. This is mainly used for logging. @@ -403,8 +395,7 @@ public void setNode(@Nullable Node node) { * @return The node currently marked as being handled. This is mainly used for logging. * Null indicates no node is currently being handled (that the ParserInstance is aware of). */ - @Nullable - public Node getNode() { + public @Nullable Node getNode() { return node; } @@ -636,9 +627,8 @@ public HashMap getCurrentOptions() { /** * @deprecated Use {@link #getCurrentStructure()} */ - @Nullable @Deprecated - public SkriptEvent getCurrentSkriptEvent() { + public @Nullable SkriptEvent getCurrentSkriptEvent() { Structure structure = getCurrentStructure(); if (structure instanceof SkriptEvent) return (SkriptEvent) structure; diff --git a/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java b/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java index e23c43c476f..c0aa1518128 100644 --- a/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java @@ -23,7 +23,7 @@ import ch.njol.skript.util.Container; import ch.njol.util.Kleenean; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.NoSuchElementException; @@ -47,14 +47,12 @@ protected Object[] get(Event e) { } @Override - @Nullable - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { Iterator> iterator = expr.iterator(event); if (iterator == null) return null; - return new Iterator() { - @Nullable - private Iterator current; + return new Iterator<>() { + private @Nullable Iterator current; @Override public boolean hasNext() { diff --git a/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java b/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java index 3e9208a2694..9338fcc73ee 100644 --- a/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java +++ b/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java @@ -47,8 +47,7 @@ public static ContextlessEvent get() { * This method should never be called. */ @Override - @NotNull - public HandlerList getHandlers() { + public @NotNull HandlerList getHandlers() { throw new IllegalStateException(); } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java index 5fff6740da5..cdda53f68bd 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java @@ -28,7 +28,7 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.ConverterInfo; import org.skriptlang.skript.lang.converter.Converters; @@ -93,8 +93,7 @@ public ConvertedExpression(Expression source, Class to, Collecti } @SafeVarargs - @Nullable - public static ConvertedExpression newInstance(Expression from, Class... to) { + public static @Nullable ConvertedExpression newInstance(Expression from, Class... to) { assert !CollectionUtils.containsSuperclass(to, from.getReturnType()); // we track a list of converters that may work List> converters = new ArrayList<>(); @@ -146,20 +145,17 @@ public boolean isSingle() { } @Override - @Nullable @SuppressWarnings("unchecked") - public Expression getConvertedExpression(Class... to) { + public @Nullable Expression getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, this.to)) return (Expression) this; return source.getConvertedExpression(to); } - @Nullable - private ClassInfo returnTypeInfo; + private @Nullable ClassInfo returnTypeInfo; @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { Class[] validClasses = source.acceptChange(mode); if (validClasses == null) { ClassInfo returnTypeInfo; @@ -171,7 +167,7 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { ClassInfo returnTypeInfo = this.returnTypeInfo; if (returnTypeInfo != null) { Changer changer = returnTypeInfo.getChanger(); @@ -183,8 +179,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { } @Override - @Nullable - public T getSingle(Event event) { + public @Nullable T getSingle(Event event) { F value = source.getSingle(event); if (value == null) return null; @@ -243,14 +238,12 @@ public boolean isLoopOf(String input) { } @Override - @Nullable - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { Iterator iterator = source.iterator(event); if (iterator == null) return null; - return new Iterator() { - @Nullable - T next = null; + return new Iterator<>() { + @Nullable T next = null; @Override public boolean hasNext() { @@ -295,8 +288,7 @@ public Expression simplify() { } @Override - @Nullable - public Object[] beforeChange(Expression changed, @Nullable Object[] delta) { + public Object @Nullable [] beforeChange(Expression changed, Object @Nullable [] delta) { return source.beforeChange(changed, delta); // Forward to source // TODO this is not entirely safe, even though probably works well enough } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java index e404deadcc6..23d8890f924 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java @@ -25,7 +25,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.ConverterInfo; import org.skriptlang.skript.lang.converter.Converters; @@ -46,9 +46,8 @@ public ConvertedLiteral(Literal source, T[] data, Class to) { } @Override - @Nullable @SuppressWarnings("unchecked") - public Literal getConvertedExpression(Class... to) { + public @Nullable Literal getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, this.to)) return (Literal) this; return ((Literal) source).getConvertedExpression(to); @@ -87,8 +86,7 @@ public T getSingle(Event event) { } @Override - @Nullable - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { return new ArrayIterator<>(data); } 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 bfaf485f851..b482204af33 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java @@ -18,13 +18,12 @@ */ package ch.njol.skript.lang.util; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.SkriptAPIException; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; /** * A very basic SkriptEvent which returns true for all events (i.e. all registered events). diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index daa44c7f9f8..d53af1042fd 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -32,8 +32,8 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.ConverterInfo; @@ -55,8 +55,7 @@ public abstract class SimpleExpression implements Expression { protected SimpleExpression() {} @Override - @Nullable - public final T getSingle(Event event) { + public final @Nullable T getSingle(Event event) { T[] values = getArray(event); if (values.length == 0) return null; @@ -66,10 +65,10 @@ public final T getSingle(Event event) { } @Override - @SuppressWarnings("unchecked") public T[] getAll(Event event) { T[] values = get(event); if (values == null) { + //noinspection unchecked T[] emptyArray = (T[]) Array.newInstance(getReturnType(), 0); assert emptyArray != null; return emptyArray; @@ -82,6 +81,7 @@ public T[] getAll(Event event) { numNonNull++; if (numNonNull == values.length) return Arrays.copyOf(values, values.length); + //noinspection unchecked T[] valueArray = (T[]) Array.newInstance(getReturnType(), numNonNull); assert valueArray != null; int i = 0; @@ -92,10 +92,10 @@ public T[] getAll(Event event) { } @Override - @SuppressWarnings("unchecked") public final T[] getArray(Event event) { T[] values = get(event); if (values == null) { + //noinspection unchecked return (T[]) Array.newInstance(getReturnType(), 0); } if (values.length == 0) @@ -110,6 +110,7 @@ public final T[] getArray(Event event) { if (values.length == 1 && values[0] != null) return Arrays.copyOf(values, 1); int rand = Utils.random(0, numNonNull); + //noinspection unchecked T[] valueArray = (T[]) Array.newInstance(getReturnType(), 1); for (T value : values) { if (value != null) { @@ -125,6 +126,7 @@ public final T[] getArray(Event event) { if (numNonNull == values.length) return Arrays.copyOf(values, values.length); + //noinspection unchecked T[] valueArray = (T[]) Array.newInstance(getReturnType(), numNonNull); int i = 0; for (T value : values) @@ -140,8 +142,7 @@ public final T[] getArray(Event event) { * @param event The event with which this expression is evaluated. * @return An array of values for this event. May not contain nulls. */ - @Nullable - protected abstract T[] get(Event event); + protected abstract T @Nullable [] get(Event event); @Override public final boolean check(Event event, Checker checker) { @@ -154,7 +155,7 @@ public final boolean check(Event event, Checker checker, boolean nega } // TODO return a kleenean (UNKNOWN if 'values' is null or empty) - public static boolean check(@Nullable T[] values, Checker checker, boolean invert, boolean and) { + public static boolean check(T @Nullable [] values, Checker checker, boolean invert, boolean and) { if (values == null) return invert; boolean hasElement = false; @@ -184,8 +185,7 @@ public static boolean check(@Nullable T[] values, Checker checker * @see ConvertedExpression#newInstance(Expression, Class...) * @see Converter */ - @Nullable - protected ConvertedExpression getConvertedExpr(Class... to) { + protected @Nullable ConvertedExpression getConvertedExpr(Class... to) { assert !CollectionUtils.containsSuperclass(to, getReturnType()); return ConvertedExpression.newInstance(this, to); } @@ -200,9 +200,8 @@ public static boolean check(@Nullable T[] values, Checker checker * @return The converted expression */ @Override - @Nullable @SuppressWarnings("unchecked") - public Expression getConvertedExpression(Class... to) { + public @Nullable Expression getConvertedExpression(Class... to) { // check whether this expression is already of type R if (CollectionUtils.containsSuperclass(to, getReturnType())) return (Expression) this; @@ -236,12 +235,10 @@ public Expression getConvertedExpression(Class... to) { return this.getConvertedExpr(to); } - @Nullable - private ClassInfo returnTypeInfo; + private @Nullable ClassInfo returnTypeInfo; @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { ClassInfo returnTypeInfo = this.returnTypeInfo; if (returnTypeInfo == null) this.returnTypeInfo = returnTypeInfo = Classes.getSuperClassInfo(getReturnType()); @@ -252,14 +249,14 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - @SuppressWarnings("unchecked") - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { ClassInfo returnTypeInfo = this.returnTypeInfo; if (returnTypeInfo == null) throw new UnsupportedOperationException(); Changer changer = returnTypeInfo.getChanger(); if (changer == null) throw new UnsupportedOperationException(); + //noinspection unchecked ((Changer) changer).change(getArray(event), delta, mode); } @@ -304,7 +301,7 @@ protected final boolean setTime(int time, Class... applicableEv return true; } - protected final boolean setTime(int time, Class applicableEvent, @NonNull Expression... mustbeDefaultVars) { + protected final boolean setTime(int time, Class applicableEvent, @NotNull Expression... mustbeDefaultVars) { if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { Skript.error("Can't use time states after the event has already passed."); return false; @@ -356,8 +353,7 @@ public boolean isLoopOf(String input) { } @Override - @Nullable - public Iterator iterator(Event event) { + public @Nullable Iterator iterator(Event event) { return new ArrayIterator<>(getArray(event)); } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index 030db308993..db7ed90f29c 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -34,7 +34,7 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.NonNullIterator; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converters; import java.lang.reflect.Array; @@ -52,8 +52,7 @@ public class SimpleLiteral implements Literal, DefaultExpression { private final boolean isDefault; private final boolean and; - @Nullable - private UnparsedLiteral source = null; + private @Nullable UnparsedLiteral source = null; protected transient T[] data; @@ -136,9 +135,8 @@ public Class getReturnType() { } @Override - @Nullable @SuppressWarnings("unchecked") - public Literal getConvertedExpression(Class... to) { + public @Nullable Literal getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, type)) return (Literal) this; R[] parsedData = Converters.convert(this.data(), to, (Class) Utils.getSuperType(to)); @@ -183,8 +181,7 @@ public boolean check(Event event, Checker checker) { private ClassInfo returnTypeInfo; @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { ClassInfo returnTypeInfo = this.returnTypeInfo; if (returnTypeInfo == null) this.returnTypeInfo = returnTypeInfo = Classes.getSuperClassInfo(getReturnType()); @@ -193,7 +190,7 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(final Event event, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { + public void change(final Event event, final Object @Nullable [] delta, final ChangeMode mode) throws UnsupportedOperationException { final ClassInfo returnTypeInfo = this.returnTypeInfo; if (returnTypeInfo == null) throw new UnsupportedOperationException(); diff --git a/src/main/java/ch/njol/skript/patterns/Keyword.java b/src/main/java/ch/njol/skript/patterns/Keyword.java index 91dca7d86b9..c4159af547b 100644 --- a/src/main/java/ch/njol/skript/patterns/Keyword.java +++ b/src/main/java/ch/njol/skript/patterns/Keyword.java @@ -18,10 +18,17 @@ */ package ch.njol.skript.patterns; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.Contract; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -42,57 +49,49 @@ abstract class Keyword { * @param first The pattern to build keywords from. * @return A list of all keywords within first. */ + @Contract("_ -> new") public static Keyword[] buildKeywords(PatternElement first) { + return buildKeywords(first, true, 0); + } + + /** + * Builds a list of keywords starting from the provided pattern element. + * @param first The pattern to build keywords from. + * @param starting Whether this is the start of a pattern. + * @return A list of all keywords within first. + */ + @Contract("_, _, _ -> new") + private static Keyword[] buildKeywords(PatternElement first, boolean starting, int depth) { List keywords = new ArrayList<>(); PatternElement next = first; - boolean starting = true; // whether it is the start of the pattern - boolean ending = next.next == null; // whether it is the end of the pattern while (next != null) { if (next instanceof LiteralPatternElement) { // simple literal strings are keywords String literal = next.toString().trim(); while (literal.contains(" ")) literal = literal.replace(" ", " "); - keywords.add(new SimpleKeyword(literal, starting, ending)); - } else if (next instanceof ChoicePatternElement) { // this element might contain some keywords - List choiceElements = flatten(next); - if (choiceElements.stream().allMatch(e -> e instanceof LiteralPatternElement)) { - // all elements are literals, and this is a choice, meaning one of them must be required - // thus, we build a keyword that requires one of them to be present. - List groupKeywords = choiceElements.stream() - .map(e -> { - String literal = e.toString().trim(); - while (literal.contains(" ")) - literal = literal.replace(" ", " "); - return literal; - }) - .collect(Collectors.toList()); - keywords.add(new GroupKeyword(groupKeywords, starting, ending)); - } - } else if (next instanceof GroupPatternElement) { // groups need to be unwrapped (they might contain choices) - next = ((GroupPatternElement) next).getPatternElement(); - continue; + if (!literal.isEmpty()) // empty string is not useful + keywords.add(new SimpleKeyword(literal, starting, next.next == null)); + } else if (depth <= 1 && next instanceof ChoicePatternElement) { // attempt to build keywords from choices + final boolean finalStarting = starting; + final int finalDepth = depth; + // build the keywords for each choice + Set> choices = ((ChoicePatternElement) next).getPatternElements().stream() + .map(element -> buildKeywords(element, finalStarting, finalDepth)) + .map(ImmutableSet::copyOf) + .collect(Collectors.toSet()); + if (choices.stream().noneMatch(Collection::isEmpty)) // each choice must have a keyword for this to work + keywords.add(new ChoiceKeyword(choices)); // a keyword where only one choice much + } else if (next instanceof GroupPatternElement) { // add in keywords from the group + Collections.addAll(keywords, buildKeywords(((GroupPatternElement) next).getPatternElement(), starting, depth + 1)); } - starting = false; - next = next.next; - } - return keywords.toArray(new Keyword[0]); - } - /** - * A method for flattening a pattern element. - * For example, a {@link ChoicePatternElement} wraps multiple elements. This method unwraps it. - * @param element The element to flatten. - * @return A list of all pattern elements contained within element. - */ - private static List flatten(PatternElement element) { - if (element instanceof ChoicePatternElement) { - return ((ChoicePatternElement) element).getPatternElements().stream() - .flatMap(e -> flatten(e).stream()) - .collect(Collectors.toList()); - } else if (element instanceof GroupPatternElement) { - element = ((GroupPatternElement) element).getPatternElement(); + // a parse tag does not represent actual content in a pattern, therefore it should not affect starting + if (!(next instanceof ParseTagPatternElement)) + starting = false; + + next = next.originalNext; } - return Collections.singletonList(element); + return keywords.toArray(new Keyword[0]); } /** @@ -118,31 +117,70 @@ public boolean isPresent(String expr) { return expr.contains(keyword); } + @Override + public int hashCode() { + return Objects.hash(keyword, starting, ending); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof SimpleKeyword)) + return false; + SimpleKeyword other = (SimpleKeyword) obj; + return this.keyword.equals(other.keyword) && + this.starting == other.starting && + this.ending == other.ending; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("keyword", keyword) + .add("starting", starting) + .add("ending", ending) + .toString(); + } + } /** * A keyword implementation that requires at least one string out of a collection of strings to be present. */ - private static final class GroupKeyword extends Keyword { + private static final class ChoiceKeyword extends Keyword { - private final Collection keywords; - private final boolean starting, ending; + private final Set> choices; - GroupKeyword(Collection keywords, boolean starting, boolean ending) { - this.keywords = keywords; - this.starting = starting; - this.ending = ending; + ChoiceKeyword(Set> choices) { + this.choices = choices; } @Override public boolean isPresent(String expr) { - if (starting) - return keywords.stream().anyMatch(expr::startsWith); - if (ending) - return keywords.stream().anyMatch(expr::endsWith); - return keywords.stream().anyMatch(expr::contains); + return choices.stream().anyMatch(keywords -> keywords.stream().allMatch(keyword -> keyword.isPresent(expr))); } + @Override + public int hashCode() { + return Arrays.hashCode(choices.toArray()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ChoiceKeyword)) + return false; + return choices.equals(((ChoiceKeyword) obj).choices); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("choices", choices.stream().map(Object::toString).collect(Collectors.joining(", "))) + .toString(); + } } } diff --git a/src/main/java/ch/njol/skript/structures/StructUsing.java b/src/main/java/ch/njol/skript/structures/StructUsing.java index 9e4d473969a..2ab5ace28fa 100644 --- a/src/main/java/ch/njol/skript/structures/StructUsing.java +++ b/src/main/java/ch/njol/skript/structures/StructUsing.java @@ -42,7 +42,7 @@ "using 1.21", "using my-cool-addon-feature" }) -@Since("INSERT VERSION") +@Since("2.9.0") public class StructUsing extends Structure { public static final Priority PRIORITY = new Priority(15); diff --git a/src/main/java/ch/njol/skript/test/runner/ExprTestStringLiteral.java b/src/main/java/ch/njol/skript/test/runner/ExprTestStringLiteral.java new file mode 100644 index 00000000000..aedd7ef5a7f --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/ExprTestStringLiteral.java @@ -0,0 +1,71 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.*; +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; + +@Name("Test String Literal") +@Description("Accepts only a string literal. Used for testing correct parsing & literal treatment. Returns the value.") +@NoDoc +public class ExprTestStringLiteral extends SimpleExpression { + + static { + if (TestMode.ENABLED) + Skript.registerExpression(ExprTestStringLiteral.class, String.class, ExpressionType.SIMPLE, "test string literal %*string%"); + } + + private Expression literal; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + this.literal = (Expression) expressions[0]; + return literal instanceof LiteralString; + } + + @Override + protected @Nullable String[] get(Event event) { + return literal.getArray(event); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "test string literal " + literal.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/SecReturnable.java b/src/main/java/ch/njol/skript/test/runner/SecReturnable.java index 436b6d282fb..03833d7132d 100644 --- a/src/main/java/ch/njol/skript/test/runner/SecReturnable.java +++ b/src/main/java/ch/njol/skript/test/runner/SecReturnable.java @@ -56,8 +56,8 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is } @Override - public void returnValues(Object @Nullable [] values) { - returnedValues = values; + public void returnValues(Event event, Expression value) { + returnedValues = value.getArray(event); } @Override @@ -110,5 +110,5 @@ public String toString(@Nullable Event event, boolean debug) { } } - + } diff --git a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java index 65a52f0ac1a..6988d537cff 100644 --- a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java +++ b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java @@ -27,8 +27,6 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Pig; -import org.junit.After; -import org.junit.Before; import ch.njol.skript.Skript; @@ -79,8 +77,6 @@ public static void setShutdownDelay(long delay) { /** * Override this method if your JUnit test requires block modification with delay over 1 tick. */ - @Before - @After public void cleanup() { getTestWorld().getEntities().forEach(Entity::remove); setBlock(Material.AIR); diff --git a/src/main/java/ch/njol/skript/util/EnchantmentType.java b/src/main/java/ch/njol/skript/util/EnchantmentType.java index 1ef13893fc9..dd8ab383128 100644 --- a/src/main/java/ch/njol/skript/util/EnchantmentType.java +++ b/src/main/java/ch/njol/skript/util/EnchantmentType.java @@ -18,30 +18,26 @@ */ package ch.njol.skript.util; -import java.util.Collection; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; - -import org.bukkit.enchantments.Enchantment; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.EnchantmentUtils; -import ch.njol.skript.localization.Language; +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 ch.njol.yggdrasil.YggdrasilSerializable; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Pattern; /** * @author Peter Güttinger */ public class EnchantmentType implements YggdrasilSerializable { - - private final static String LANGUAGE_NODE = "enchantments"; - + + private static @Nullable Parser ENCHANTMENT_PARSER = null; private final Enchantment type; private final int level; - + /** * Used for deserialisation only */ @@ -50,39 +46,41 @@ private EnchantmentType() { type = null; level = -1; } - + public EnchantmentType(final Enchantment type) { assert type != null; this.type = type; this.level = -1; } + public EnchantmentType(final Enchantment type, final int level) { assert type != null; this.type = type; this.level = level; } - + /** * @return level or 1 if level == -1 */ public int getLevel() { return level == -1 ? 1 : level; } - + /** * @return the internal level, can be -1 */ public int getInternalLevel() { return level; } - + @Nullable public Enchantment getType() { return type; } - + /** * Checks whether the given item type has this enchantment. + * * @param item the item to be checked. * @deprecated Use {@link ItemType#hasEnchantments(Enchantment...)} */ @@ -90,77 +88,41 @@ public Enchantment getType() { public boolean has(final ItemType item) { return item.hasEnchantments(type); } - + @Override public String toString() { - return toString(type) + (level == -1 ? "" : " " + level); - } - - @SuppressWarnings("null") - public static String toString(final Enchantment e) { - return NAMES.get(e); + return getEnchantmentParser().toString(type, 0) + (level == -1 ? "" : " " + level); } - - // REMIND flags? - @SuppressWarnings("null") - public static String toString(final Enchantment e, final int flags) { - return NAMES.get(e); - } - - private final static Map NAMES = new HashMap<>(); - private final static Map PATTERNS = new HashMap<>(); - - static { - Language.addListener(() -> { - NAMES.clear(); - for (Enchantment e : Enchantment.values()) { - assert e != null; - final String[] names = Language.getList(LANGUAGE_NODE + ".names." + EnchantmentUtils.getKey(e)); - NAMES.put(e, names[0]); - - for (String name : names) - PATTERNS.put(name.toLowerCase(Locale.ENGLISH), e); - } - }); - } - + @SuppressWarnings("null") private final static Pattern pattern = Pattern.compile(".+ \\d+"); - + /** * Parses an enchantment type from string. This includes an {@link Enchantment} * and its level. + * * @param s String to parse. * @return Enchantment type, or null if parsing failed. */ @Nullable public static EnchantmentType parse(final String s) { + Parser enchantmentParser = getEnchantmentParser(); if (pattern.matcher(s).matches()) { String name = s.substring(0, s.lastIndexOf(' ')); assert name != null; - final Enchantment ench = parseEnchantment(name); + final Enchantment ench = enchantmentParser.parse(name, ParseContext.DEFAULT); if (ench == null) return null; String level = s.substring(s.lastIndexOf(' ') + 1); assert level != null; return new EnchantmentType(ench, Utils.parseInt(level)); } - final Enchantment ench = parseEnchantment(s); + final Enchantment ench = enchantmentParser.parse(s, ParseContext.DEFAULT); if (ench == null) return null; return new EnchantmentType(ench, -1); } - - @Nullable - public static Enchantment parseEnchantment(final String s) { - return PATTERNS.get(s.toLowerCase(Locale.ENGLISH)); - } - - @SuppressWarnings("null") - public static Collection getNames() { - return NAMES.values(); - } - + @Override public int hashCode() { final int prime = 31; @@ -169,7 +131,7 @@ public int hashCode() { result = prime * result + type.hashCode(); return result; } - + @Override public boolean equals(final @Nullable Object obj) { if (this == obj) @@ -183,5 +145,20 @@ public boolean equals(final @Nullable Object obj) { return false; return type.equals(other.type); } - + + @SuppressWarnings("unchecked") + private static Parser getEnchantmentParser() { + if (ENCHANTMENT_PARSER == null) { + ClassInfo classInfo = Classes.getExactClassInfo(Enchantment.class); + if (classInfo == null) { + throw new IllegalStateException("Enchantment ClassInfo not found"); + } + ENCHANTMENT_PARSER = (Parser) classInfo.getParser(); + if (ENCHANTMENT_PARSER == null) { + throw new IllegalStateException("Enchantment parser not found"); + } + } + return ENCHANTMENT_PARSER; + } + } diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index ff70f8b365f..3b192a34dff 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.util; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Locale; - -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; - import ch.njol.skript.Skript; import ch.njol.skript.localization.GeneralWords; import ch.njol.skript.localization.Language; @@ -38,6 +26,17 @@ import ch.njol.util.NonNullPair; import ch.njol.util.coll.CollectionUtils; import ch.njol.yggdrasil.YggdrasilSerializable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Timespan implements YggdrasilSerializable, Comparable { // REMIND unit @@ -211,7 +210,7 @@ public Timespan(TimePeriod timePeriod, long time) { /** * Builds a Timespan from the given long parameter. * - * @deprecated Use {@link Timespan#Timespan(TimePeriod, long)} + * @deprecated Use {@link #Timespan(TimePeriod, long)} * * @param ticks The amount of Minecraft ticks to convert to a timespan. * @return Timespan based on the provided long. @@ -257,7 +256,7 @@ public long getTicks() { * @return the amount of TimePeriod this timespan represents. */ public long getAs(TimePeriod timePeriod) { - return Math.round(millis * timePeriod.getTime()); + return millis / timePeriod.getTime(); } /** diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 862e440ceb8..879e1aabe74 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -641,7 +641,7 @@ public String run(final Matcher m) { return "" + m; } - private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#?[0-9a-f]{6}"); + private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#{0,2}[0-9a-f]{6}"); /** * Tries to get a {@link ChatColor} from the given string. diff --git a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java index 5ab854e68c8..47f229bda23 100644 --- a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java +++ b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java @@ -20,6 +20,7 @@ import java.util.Locale; +import org.bukkit.entity.Entity; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -132,10 +133,18 @@ public void set(final EntityEquipment e, final @Nullable ItemStack item) { private final EntityEquipment e; private final EquipSlot slot; + private final int slotIndex; private final boolean slotToString; public EquipmentSlot(final EntityEquipment e, final EquipSlot slot, final boolean slotToString) { this.e = e; + int slotIndex = -1; + if (slot == EquipSlot.TOOL) { + Entity holder = e.getHolder(); + if (holder instanceof Player) + slotIndex = ((Player) holder).getInventory().getHeldItemSlot(); + } + this.slotIndex = slotIndex; this.slot = slot; this.slotToString = slotToString; } @@ -146,10 +155,12 @@ public EquipmentSlot(final EntityEquipment e, final EquipSlot slot) { @SuppressWarnings("null") public EquipmentSlot(HumanEntity holder, int index) { - this.e = holder.getEquipment(); - this.slot = values[41 - index]; // 6 entries in EquipSlot, indices descending - // So this math trick gets us the EquipSlot from inventory slot index - this.slotToString = true; // Referring to numeric slot id, right? + /* + * slot: 6 entries in EquipSlot, indices descending + * So this math trick gets us the EquipSlot from inventory slot index + * slotToString: Referring to numeric slot id, right? + */ + this(holder.getEquipment(), values[41 - index], true); } @Override @@ -189,7 +200,8 @@ public EquipSlot getEquipSlot() { @Override public int getIndex() { - return slot.slotNumber; + // use specific slotIndex if available + return slotIndex != -1 ? slotIndex : slot.slotNumber; } @Override diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java index dd9d31456c9..e8f4beb7333 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -151,8 +151,11 @@ private static void registerDataSupplier(String id, BiFunction extends KeyValueEntryData returnType; + private final Class[] returnTypes; private final int flags; @@ -44,10 +44,9 @@ public class ExpressionEntryData extends KeyValueEntryData defaultValue, boolean optional, - Class returnType + String key, @Nullable Expression defaultValue, boolean optional, Class returnType ) { - this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS); + this(key, defaultValue, optional, SkriptParser.ALL_FLAGS, returnType); } /** @@ -56,11 +55,32 @@ public ExpressionEntryData( * javadoc for more details. */ public ExpressionEntryData( - String key, @Nullable Expression defaultValue, boolean optional, - Class returnType, int flags + String key, @Nullable Expression defaultValue, boolean optional, Class returnType, int flags + ) { + this(key, defaultValue, optional, flags, returnType); + } + + /** + * @param returnTypes The expected return types of the matched expression. + */ + @SafeVarargs + public ExpressionEntryData( + String key, @Nullable Expression defaultValue, boolean optional, Class... returnTypes + ) { + this(key, defaultValue, optional, SkriptParser.ALL_FLAGS, returnTypes); + } + + /** + * @param returnTypes The expected return types of the matched expression. + * @param flags Parsing flags. See {@link SkriptParser#SkriptParser(String, int, ParseContext)} + * javadoc for more details. + */ + @SafeVarargs + public ExpressionEntryData( + String key, @Nullable Expression defaultValue, boolean optional, int flags, Class... returnTypes ) { super(key, defaultValue, optional); - this.returnType = returnType; + this.returnTypes = returnTypes; this.flags = flags; } @@ -71,10 +91,10 @@ protected Expression getValue(String value) { Expression expression; try (ParseLogHandler log = new ParseLogHandler().start()) { expression = new SkriptParser(value, flags, ParseContext.DEFAULT) - .parseExpression(returnType); + .parseExpression(returnTypes); if (expression == null) // print an error if it couldn't parse log.printError( - "'" + value + "' " + M_IS + " " + SkriptParser.notOfType(returnType), + "'" + value + "' " + M_IS + " " + SkriptParser.notOfType(returnTypes), ErrorQuality.NOT_AN_EXPRESSION ); } diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 68d9bfd926f..ff5e1e9e40a 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -22,7 +22,7 @@ # This file, all scripts and other files ending in .sk are NOT .yml/YAML files, but very similar! # Please remember the following when editing files: # - To indent sections you can use spaces like in YAML, but tabs are also allowed. Just remember to stick to the one or the other for a section/trigger. -# - '#' starts a comment like in YAML. If you don't want it to start a comment simply double it: '##' (You also have to double these in "quoted text") +# - '#' starts a comment like in YAML. If you don't want it to start a comment simply double it: '##' (You do NOT have to double these in "quoted text") # - If you use special characters (§, äöü, éèàôç, ñ, etc.) you have to encode the file in UTF-8. # @@ -80,6 +80,16 @@ log effect commands: false # Whether Skript should log the usage of effect commands. # They will be logged as [INFORMATION] in this format: ' issued effect command: ' + +load default aliases: true +# Whether Skript should use the default, Skript-provided aliases for items. +# Disabling this will cause all aliases to be automatically generated. Some items, like potions, +# will likely not have aliases in this case. +# +# You can provide your own aliases by making an 'aliases' folder in the Skript directory and putting the alias files there. +# The default aliases folder is available with Skript releases on GitHub. + + player variable fix: true # Whether to enable the player variable fix if a player has rejoined and was reciding inside a variable. # Player objects inside a variable(list or normal) are not updated to the new player object @@ -122,6 +132,9 @@ plugin priority: high # - effects 'remove ... from drops'/'clear drops': Drops added by other plugins are not removed => increase priority # Skript removes drops it shouldn't => decrease priority or specify which item types to remove +listen to cancelled events by default: false +# Determines whether `on ` will be triggered by all events (true) or only uncancelled events (false). +# The default is false, which maintains traditional Skript behavior. number accuracy: 2 # How many digits should be displayed after the dot at maximum when displaying numbers. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 701cd9d07b9..59fa474fec6 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -13,6 +13,7 @@ not: not # not a: not a(n) # @a:not a @an:not an @x:not neither: neither nor: nor +of: of genders: 0: @@ -47,58 +48,56 @@ aliases: # -- Enchantments -- enchantments: - of: of - names: - protection: Protection - fire_protection: Fire Protection - feather_falling: Feather Falling - blast_protection: Blast Protection - projectile_protection: Projectile Protection - respiration: Respiration - aqua_affinity: Aqua Affinity - sharpness: Sharpness - smite: Smite - bane_of_arthropods: Bane of Arthropods - knockback: Knockback - fire_aspect: Fire Aspect - looting: Looting - efficiency: Efficiency - silk_touch: Silk Touch - unbreaking: Unbreaking - fortune: Fortune - power: Power - punch: Punch - flame: Flame - infinity: Infinity - thorns: Thorns - luck: Luck of the Sea - lure: Lure - depth_strider: Depth Strider - mending: Mending - frost_walker: Frost Walker - vanishing_curse: Curse of Vanishing - binding_curse: Curse of Binding - # It's sweeping on 1.10 and below for some reason. - sweeping: Sweeping Edge - sweeping_edge: Sweeping Edge - # New 1.13 Enchantments - channeling: Channeling, Channelling - riptide: Riptide - impaling: Impaling - loyalty: Loyalty - luck_of_the_sea: Luck of The Sea - # new 1.14 Enchantments - multishot: Multishot, Multi-Shot - piercing: Piercing - quick_charge: Quick Charge - # new 1.16 Enchantment - soul_speed: Soul Speed - # new 1.19 Enchantment - swift_sneak: Swift Sneak - # new 1.21 Enchantments (added in 1.20.5 experimental) - density: Density - breach: Breach - wind_burst: Wind Burst + protection: Protection + fire_protection: Fire Protection + feather_falling: Feather Falling + blast_protection: Blast Protection + projectile_protection: Projectile Protection + respiration: Respiration + aqua_affinity: Aqua Affinity + sharpness: Sharpness + smite: Smite + bane_of_arthropods: Bane of Arthropods + knockback: Knockback + fire_aspect: Fire Aspect + looting: Looting + efficiency: Efficiency + silk_touch: Silk Touch + unbreaking: Unbreaking + fortune: Fortune + power: Power + punch: Punch + flame: Flame + infinity: Infinity + thorns: Thorns + luck: Luck of the Sea + lure: Lure + depth_strider: Depth Strider + mending: Mending + frost_walker: Frost Walker + vanishing_curse: Curse of Vanishing + binding_curse: Curse of Binding + # It's sweeping on 1.10 and below for some reason. + sweeping: Sweeping Edge + sweeping_edge: Sweeping Edge + # New 1.13 Enchantments + channeling: Channeling, Channelling + riptide: Riptide + impaling: Impaling + loyalty: Loyalty + luck_of_the_sea: Luck of The Sea + # new 1.14 Enchantments + multishot: Multishot, Multi-Shot + piercing: Piercing + quick_charge: Quick Charge + # new 1.16 Enchantment + soul_speed: Soul Speed + # new 1.19 Enchantment + swift_sneak: Swift Sneak + # new 1.21 Enchantments (added in 1.20.5 experimental) + density: Density + breach: Breach + wind_burst: Wind Burst # -- Potion Effects -- potions: @@ -1062,7 +1061,7 @@ entities: pattern: salmon(|1¦s) puffer fish: name: puffer fish¦es - pattern: puffer fish(|1¦es) + pattern: puffer[ ]fish(|1¦es) tropical fish: name: tropical fish¦es pattern: tropical fish(|1¦es) @@ -1329,6 +1328,8 @@ damage causes: sonic_boom: sonic boom kill: kill, killed world_border: world border + # 1.21 + campfire: campfire # -- Teleport Causes -- teleport causes: @@ -2098,8 +2099,10 @@ spawn reasons: natural: natural nether_portal: nether portal ocelot_baby: ocelot baby + ominous_item_spawner: ominous item spawner patrol: patrol piglin_zombified: piglin zombification + potion_effect: potion effect raid: raid reinforcements: reinforcements sheared: shear, sheared @@ -2112,6 +2115,9 @@ spawn reasons: trap: trap village_defense: village defense, golem defense, iron golem defense village_invasion: village invasion, village invading + # 1.21 + trial_spawner: trial spawner, trial mob spawner, trial creature spawner + enchantment: enchantment # -- Difficulties -- difficulties: @@ -2164,11 +2170,14 @@ genes: # -- Attribute Types -- attribute types: + # Enum variation generic_armor: generic armor, armor generic_armor_toughness: generic armor toughness, armor toughness generic_attack_damage: generic attack damage, attack damage generic_attack_knockback: generic attack knockback, attack knockback generic_attack_speed: generic attack speed, attack speed + generic_burning_time: generic burning time, burning time + generic_explosion_knockback_resistance: generic explosion knockback resistance, explosion knockback resistance generic_flying_speed: generic flying speed, flying speed generic_follow_range: generic follow range, follow range generic_gravity: generic gravity, gravity @@ -2177,16 +2186,56 @@ attribute types: generic_luck: generic luck, luck generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health + generic_movement_efficiency: generic movement efficiency, movement efficiency generic_movement_speed: generic movement speed, movement speed + generic_oxygen_bonus: generic oxygen bonus, oxygen bonus generic_safe_fall_distance: generic safe fall distance, safe fall distance generic_fall_damage_multiplier: generic fall damage multiplier, fall damage multiplier generic_scale: generic scale, scale generic_step_height: generic step height, step height + generic_water_movement_efficiency: generic water movement efficiency, water movement efficiency horse_jump_strength: horse jump strength player_block_break_speed: player block break speed, block break speed player_block_interaction_range: player block interaction range, block interaction range player_entity_interaction_range: player entity interaction range, entity interaction range + player_mining_efficiency: player mining efficiency, mining efficiency + player_sneaking_speed: player sneaking speed, sneaking speed + player_submerged_mining_speed: player submerged mining speed, submerged mining speed + player_sweeping_damage_ratio: player sweeping damage ratio, sweeping damage ratio zombie_spawn_reinforcements: zombie spawn reinforcements + # Registry variation (minecraft keys) + generic.armor: generic armor, armor + generic.armor_toughness: generic armor toughness, armor toughness + generic.attack_damage: generic attack damage, attack damage + generic.attack_knockback: generic attack knockback, attack knockback + generic.attack_speed: generic attack speed, attack speed + generic.burning_time: generic burning time, burning time + generic.explosion_knockback_resistance: generic explosion knockback resistance, explosion knockback resistance + generic.flying_speed: generic flying speed, flying speed + generic.follow_range: generic follow range, follow range + generic.gravity: generic gravity, gravity + generic.jump_strength: generic jump strength, jump strength + generic.knockback_resistance: generic knockback resistance, knockback resistance + generic.luck: generic luck, luck + generic.max_absorption: generic max absorption, max absorption + generic.max_health: generic max health, max health + generic.movement_efficiency: generic movement efficiency, movement efficiency + generic.movement_speed: generic movement speed, movement speed + generic.oxygen_bonus: generic oxygen bonus, oxygen bonus + generic.safe_fall_distance: generic safe fall distance, safe fall distance + generic.fall_damage_multiplier: generic fall damage multiplier, fall damage multiplier + generic.scale: generic scale, scale + generic.step_height: generic step height, step height + generic.water_movement_efficiency: generic water movement efficiency, water movement efficiency + horse_jump_strength: horse jump strength + player.block_break_speed: player block break speed, block break speed + player.block_interaction_range: player block interaction range, block interaction range + player.entity_interaction_range: player entity interaction range, entity interaction range + player.mining_efficiency: player mining efficiency, mining efficiency + player.sneaking_speed: player sneaking speed, sneaking speed + player.submerged_mining_speed: player submerged mining speed, submerged mining speed + player.sweeping_damage_ratio: player sweeping damage ratio, sweeping damage ratio + zombie.spawn_reinforcements: zombie spawn reinforcements # -- Environments -- environments: @@ -2275,7 +2324,7 @@ types: itemstack: item stack¦s @an itementity: dropped item¦s @a # same as entities.dropped item.name biome: biome¦s @a - potioneffecttype: potion¦s @a + potioneffecttype: potion effect type¦s @a potioneffect: potion effect¦s @a enchantment: enchantment¦s @an damagecause: damage cause¦s @a diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index 9daa56b74c2..b502f9068aa 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -15,7 +15,7 @@ skript: invalid reload: Skript只能通过Bukkit的“/reload”或Skript的“/skript reload”命令来重新加载。 no scripts: 没有发现任何脚本,也许你应该写一些 ;) no errors: 所有脚本都已加载,没有错误。 - scripts loaded: 已加载%s个脚本,包括%s个结构和%s个命令(%s内) + scripts loaded: 已加载%s个脚本,总共包含%s个结构。 (耗时%s) finished loading: 已完成加载。 # -- Skript command -- diff --git a/src/main/resources/scripts/-examples/text formatting.sk b/src/main/resources/scripts/-examples/text formatting.sk index 8a5a1ab277a..5163ca116c3 100644 --- a/src/main/resources/scripts/-examples/text formatting.sk +++ b/src/main/resources/scripts/-examples/text formatting.sk @@ -4,7 +4,7 @@ # You can also use <> for colours and formats, like `` for red and `` for bold # # In Minecraft 1.16, support was added for 6-digit hexadecimal colors to specify custom colors other than the 16 default color codes. -# The tag for these colors looks like this: <##hex code> e.g. `<##123456>` +# The tag for these colors looks like this: <#hex code> e.g. `<#123456>` # command /color: @@ -12,7 +12,7 @@ command /color: trigger: send "&6This message is golden." send "This message is light red and bold." - send "<##FF0000>This message is red." + send "<#FF0000>This message is red." # # Other formatting options are also available. diff --git a/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java index c7e0eb3b29f..b0a7ff2b606 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java @@ -47,6 +47,7 @@ public void splitLineTest() { {"#########", "", "#########"}, {"a##b#c##d#e", "a#b", "#c##d#e"}, {" a ## b # c ## d # e ", " a # b ", "# c ## d # e "}, + {"a b \"#a ##\" # b \"", "a b \"#a ##\" ", "# b \""}, }; for (String[] d : data) { NonNullPair p = Node.splitLine(d[0]); diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/BlockDataNotCloned6829.java b/src/test/java/org/skriptlang/skript/test/tests/regression/BlockDataNotCloned6829.java new file mode 100644 index 00000000000..b621734958d --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/BlockDataNotCloned6829.java @@ -0,0 +1,53 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.regression; + +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.block.data.type.Tripwire; +import org.bukkit.event.Event; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Objects; + +public class BlockDataNotCloned6829 extends SkriptJUnitTest { + + public void run(String unparsedEffect, Event event) { + Effect effect = Effect.parse(unparsedEffect, "Can't understand this effect: " + unparsedEffect); + if (effect == null) + throw new IllegalStateException(); + TriggerItem.walk(effect, event); + } + + @Test + public void test() { + Event event = ContextlessEvent.get(); + run("set {_original tripwire} to tripwire[]", event); + run("set {_another tripwire} to {_original tripwire}", event); + Tripwire originalTripwire = (Tripwire) Objects.requireNonNull(Variables.getVariable("original tripwire", event, true)); + Tripwire anotherTripwire = (Tripwire) Objects.requireNonNull(Variables.getVariable("another tripwire", event, true)); + anotherTripwire.setDisarmed(true); + Assert.assertFalse(originalTripwire.isDisarmed()); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java b/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java index bb462ba0817..d5c4a3653ef 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java @@ -52,7 +52,7 @@ public void test() { ContextlessEvent event = ContextlessEvent.get(); Variables.setVariable("item", itemType, event, true); - EasyMock.expect(itemType.getRandom()).andReturn(new ItemStack(Material.STONE)).atLeastOnce(); + EasyMock.expect(itemType.getMaterial()).andReturn(Material.STONE).atLeastOnce(); EasyMock.replay(itemType); TriggerItem.walk(getPlainRandomItemEffect, event); EasyMock.verify(itemType); diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprDropsTest.java similarity index 94% rename from src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java rename to src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprDropsTest.java index f9216d5352c..f9f9c372176 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprDropsTest.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package org.skriptlang.skript.test.tests.syntaxes; +package org.skriptlang.skript.test.tests.syntaxes.expressions; import ch.njol.skript.test.runner.SkriptJUnitTest; import org.bukkit.entity.Pig; diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprMessageTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprMessageTest.java new file mode 100644 index 00000000000..1b0cbefa703 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprMessageTest.java @@ -0,0 +1,53 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.expressions; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.PluginManager; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +public class ExprMessageTest extends SkriptJUnitTest { + + private Player testPlayer; + + @Before + public void setup() { + testPlayer = EasyMock.niceMock(Player.class); + } + + @Test + public void test() { + Set viewers = new HashSet<>(); + viewers.add(testPlayer); + PluginManager manager = Bukkit.getServer().getPluginManager(); + manager.callEvent(new AsyncPlayerChatEvent(false, testPlayer, "hi", viewers)); + manager.callEvent(new PlayerJoinEvent(testPlayer, "hi")); + manager.callEvent(new PlayerQuitEvent(testPlayer, "hi")); + } +} diff --git a/src/test/skript/environments/java11/paper-1.13.2.json b/src/test/skript/environments/java11/paper-1.13.2.json deleted file mode 100644 index e6efb949c03..00000000000 --- a/src/test/skript/environments/java11/paper-1.13.2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.13.2", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.13.2", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" - ] -} diff --git a/src/test/skript/environments/java11/paper-1.14.4.json b/src/test/skript/environments/java11/paper-1.14.4.json deleted file mode 100644 index 1e2972dd84c..00000000000 --- a/src/test/skript/environments/java11/paper-1.14.4.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.14.4", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.14.4", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" - ] -} diff --git a/src/test/skript/environments/java11/paper-1.15.2.json b/src/test/skript/environments/java11/paper-1.15.2.json deleted file mode 100644 index 08a849ff98d..00000000000 --- a/src/test/skript/environments/java11/paper-1.15.2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.15.2", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.15.2", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar", "--nogui" - ] -} diff --git a/src/test/skript/environments/java11/paper-1.16.5.json b/src/test/skript/environments/java11/paper-1.16.5.json deleted file mode 100644 index 690a180c001..00000000000 --- a/src/test/skript/environments/java11/paper-1.16.5.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.16.5", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.16.5", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar", "--nogui" - ] -} diff --git a/src/test/skript/environments/java17/paper-1.18.2.json b/src/test/skript/environments/java17/paper-1.18.2.json deleted file mode 100644 index 406bdd99c06..00000000000 --- a/src/test/skript/environments/java17/paper-1.18.2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.18.2", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.18.2", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar", "--nogui" - ] -} diff --git a/src/test/skript/environments/java17/paper-1.17.1.json b/src/test/skript/environments/java21/paper-1.21.0.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.17.1.json rename to src/test/skript/environments/java21/paper-1.21.0.json index 720eba10ef5..22a72e3a73b 100644 --- a/src/test/skript/environments/java17/paper-1.17.1.json +++ b/src/test/skript/environments/java21/paper-1.21.0.json @@ -1,11 +1,11 @@ { - "name": "paper-1.17.1", + "name": "paper-1.21.0", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.17.1", + "version": "1.21", "target": "paperclip.jar" } ], diff --git a/src/test/skript/junit/ExprDrops.sk b/src/test/skript/junit/ExprDrops.sk index 3df908f9f03..c13fe31671f 100644 --- a/src/test/skript/junit/ExprDrops.sk +++ b/src/test/skript/junit/ExprDrops.sk @@ -12,13 +12,13 @@ test "ExprDropsJUnit" when running JUnit: set {_tests::11} to "add and remove experience from drops doesn't modify items" set {_tests::12} to "remove all experience from drops doesn't modify items" set {_tests::13} to "drops test complete" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" completes {_tests::*} + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprDropsTest" completes {_tests::*} # NOTE: Do NOT take the behavior described in this test as a guide for how ExprDrops SHOULD work, only for how it DOES work in 2.7.x. # The behavior should change in 2.8 and this test will be updated accordingly. on death of pig: - set {_test} to "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprDropsTest" junit test is {_test} # Items diff --git a/src/test/skript/junit/ExprMessage.sk b/src/test/skript/junit/ExprMessage.sk new file mode 100644 index 00000000000..656e4d88109 --- /dev/null +++ b/src/test/skript/junit/ExprMessage.sk @@ -0,0 +1,44 @@ +test "ExprMessageJUnit" when running JUnit: + set {_tests::1} to "set chat message" + set {_tests::2} to "clear chat message" + set {_tests::3} to "set join message" + set {_tests::4} to "clear join message" + set {_tests::5} to "set quit message" + set {_tests::6} to "clear quit message" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprMessageTest" completes {_tests::*} + +on chat: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprMessageTest" + junit test is {_test} + + set chat message to "hello" + if chat message is "hello": + complete objective "set chat message" for {_test} + + clear message + if message is "": + complete objective "clear chat message" for {_test} + +on join: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprMessageTest" + junit test is {_test} + + set login message to "I joined" + if log in message is "I joined": + complete objective "set join message" for {_test} + + clear join message + if join message is "": + complete objective "clear join message" for {_test} + +on quit: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprMessageTest" + junit test is {_test} + + set logout message to "I left" + if log out message is "I left": + complete objective "set quit message" for {_test} + + clear quit message + if leave message is "": + complete objective "clear quit message" for {_test} \ No newline at end of file diff --git a/src/test/skript/tests/misc/comments.sk b/src/test/skript/tests/misc/comments.sk new file mode 100644 index 00000000000..348f7560d6d --- /dev/null +++ b/src/test/skript/tests/misc/comments.sk @@ -0,0 +1,46 @@ +test "comments": + parse: + set {_a} to {_b} # test + assert last parse logs is not set with "skript should be able to handle inline comments but did not" + + + parse: + assert "a" is "a" with "wrong number of hashtags" + assert "#a" is join "#", and "a" with "wrong number of hashtags" + assert "##a" is join "#", "#", and "a" with "wrong number of hashtags" + assert "###a" is join "#", "#", "#", and "a" with "wrong number of hashtags" + assert last parse logs is not set with "skript should be able to handle strings any number of hashtags but did not" + + + parse: + assert "a%"#"%" is join "a", and "#" with "wrong number of hashtags" + assert "#a%"#}"%" is join "#", "a", and "#}" with "wrong number of hashtags" + assert "##a%"#"%" is join "#", "#", "a", and "#" with "wrong number of hashtags" + assert "#{##a%"#"%" is join "#{", "#", "#", "a", and "#" with "wrong number of hashtags" + assert last parse logs is not set with "skript should be able to handle complex strings any number of hashtags but did not" + + + parse: + set {_a} to "<#abcdef>test" + set {_b} to "<##abcdef>test" + assert uncoloured {_a} is "test" with "failed to parse single hashtag colour code" + assert uncoloured {_b} is "test" with "failed to parse double hashtag colour code" + assert last parse logs is not set with "skript should be able to handle hex colour codes but did not" + + parse: + set {_a} to "###SH#JABJ#BJ#JB#K#BH#G#J##J#HJ%%KJB#JKK%%""%%""%%""%%#""##%%""#""%%##""#%""%##%"#"""%#"#!!""#""#L@$L@:@#L@K:L%@^$:"#^#:^J$%:K^J%&LK:#::&&^^^%%test + assert last parse logs is not set with "skript should be able to handle very messy string but did not" + + + parse: + set {_a##} to "test" + set {##_b} to "test" + set {##_b::%"{}"%} to "test" + set {##_b::%"{}#"%} to "#test" + assert {##_b::%"{}#"%} is join "#" and "test" with "failed state machine check" + assert last parse logs is not set with "skript should be able to handle hashtags in variable names but did not" + + + parse: + set {##_b::%"{}"#%} to "#test" + assert last parse logs is set with "skript should not be able to handle hashtags in an expression in a variable name but did" diff --git a/src/test/skript/tests/misc/registry.sk b/src/test/skript/tests/misc/registry.sk new file mode 100644 index 00000000000..2294472fbae --- /dev/null +++ b/src/test/skript/tests/misc/registry.sk @@ -0,0 +1,8 @@ +test "registry" when minecraft version is "1.14": + + # Test namespaced keys + assert curse of vanishing = minecraft:vanishing_curse with "'curse of vanishing' enchant should match namespace key" + + # Test serialization + set {test::enchantment} to minecraft:sharpness + assert {test::enchantment} = sharpness with "variable should have been set to sharpness enchantment" diff --git a/src/test/skript/tests/misc/string literals.sk b/src/test/skript/tests/misc/string literals.sk new file mode 100644 index 00000000000..a8eb492de69 --- /dev/null +++ b/src/test/skript/tests/misc/string literals.sk @@ -0,0 +1,20 @@ +test "string literals": + set {_c} to (test string literal "blob blob blob") + assert {_c} is "blob blob blob" with "string literal value wrong" + assert test string literal "blob blob blob" is "blob blob blob" with "string literal value wrong" + +test "string literals (parsing)": + + parse: + set {_test} to test string literal "hello there this is a long string blah blah blah" + assert last parse logs is not set with "skript should be able to understand literal: %last parse logs%" + + parse: + set {_test} to test string literal "hello %% percent" + assert last parse logs is not set with "percents shouldn't invalidate literal: %last parse logs%" + + parse: + set {_test} to test string literal "hello %now%" + assert last parse logs is set with "the non-literal should not have been accepted" + + assert (test string literal "hello") is "hello" with "string literal value wrong" diff --git a/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk b/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk index ad8ecff83d5..4afe12bc824 100644 --- a/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk +++ b/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk @@ -1,12 +1,12 @@ test "floating point errors in rounding functions": - #assert ceil(100*0.07) is 7 with "ceil function doesn't adjust for floating point errors ##1" - #assert ceil(100*0.033 - 0.3) is 3 with "ceil function doesn't adjust for floating point errors ##2" + #assert ceil(100*0.07) is 7 with "ceil function doesn't adjust for floating point errors" + #assert ceil(100*0.033 - 0.3) is 3 with "ceil function doesn't adjust for floating point errors" - #assert rounded up 100*0.07 is 7 with "ceil expression doesn't adjust for floating point errors ##1" - #assert rounded up 100*0.033 - 0.3 is 3 with "ceil expression doesn't adjust for floating point errors ##2" + #assert rounded up 100*0.07 is 7 with "ceil expression doesn't adjust for floating point errors" + #assert rounded up 100*0.033 - 0.3 is 3 with "ceil expression doesn't adjust for floating point errors" set {_sum} to 0 loop 100 times: add 0.1 to {_sum} - assert floor({_sum}) is 10 with "floor function doesn't adjust for floating point errors ##1" - assert rounded down {_sum} is 10 with "floor expression doesn't adjust for floating point errors ##1" + assert floor({_sum}) is 10 with "floor function doesn't adjust for floating point errors" + assert rounded down {_sum} is 10 with "floor expression doesn't adjust for floating point errors" diff --git a/src/test/skript/tests/regressions/4664-formatted time.sk b/src/test/skript/tests/regressions/4664-formatted time.sk index 329c06bb9fa..a43029af91b 100644 --- a/src/test/skript/tests/regressions/4664-formatted time.sk +++ b/src/test/skript/tests/regressions/4664-formatted time.sk @@ -3,18 +3,18 @@ test "formatted time": set {_now} to now set {_date1} to {_now} formatted - assert {_date1} = {_now} formatted as {_default} with "default date format failed ##1" + assert {_date1} = {_now} formatted as {_default} with "default date format failed" set {_date2} to {_now} formatted human-readable - assert {_date2} = {_now} formatted as {_default} with "default date format failed ##2" + assert {_date2} = {_now} formatted as {_default} with "default date format failed" set {_date3} to {_now} formatted as "HH:mm" - assert length of {_date3} = 5 with "custom date format failed ##1" + assert length of {_date3} = 5 with "custom date format failed" set {_cFormat} to "hh:mm" set {_date4} to {_now} formatted as {_cFormat} - assert length of {_date4} = 5 with "custom date format failed ##2" + assert length of {_date4} = 5 with "custom date format failed" set {_cFormat2} to "i" set {_date5} to {_now} formatted as {_cFormat2} - assert {_date5} is not set with "custom date format failed ##3" + assert {_date5} is not set with "custom date format failed" diff --git a/src/test/skript/tests/regressions/4769-fire-visualeffect.sk b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk index 3965e6c877f..c490fa9e9cb 100644 --- a/src/test/skript/tests/regressions/4769-fire-visualeffect.sk +++ b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk @@ -8,8 +8,8 @@ test "fire visual effect comparison": set block at spawn of world "world" to {_block} play 5 fire at spawn of world "world" assert "fire" parsed as visual effect is fire with "failed to compare visual effects" - assert "fire" parsed as visual effect is "fire" parsed as itemtype to fail with "failed to compare visual effects ##2" + assert "fire" parsed as visual effect is "fire" parsed as itemtype to fail with "failed to compare visual effects" spawn a chicken at spawn of world "world": assert event-entity is a chicken with "failed to compare a chicken" - assert event-entity is a "chicken" parsed as itemtype to fail with "failed to compare a chicken ##2" + assert event-entity is a "chicken" parsed as itemtype to fail with "failed to compare a chicken" clear event-entity diff --git a/src/test/skript/tests/regressions/535-math is done in the wrong order.sk b/src/test/skript/tests/regressions/535-math is done in the wrong order.sk index 665ce0346f8..b404c89846d 100644 --- a/src/test/skript/tests/regressions/535-math is done in the wrong order.sk +++ b/src/test/skript/tests/regressions/535-math is done in the wrong order.sk @@ -1,10 +1,10 @@ test "math order": - assert 1 + 1 = 2 with "basic math ##1 failed" - assert 5 - 3 = 2 with "basic math ##2 failed" + assert 1 + 1 = 2 with "basic math failed" + assert 5 - 3 = 2 with "basic math failed" - assert 5 - 3 - 1 = 1 with "basic chained math ##1 failed" - assert 5-3-2 = 0 with "basic chained math ##2 failed" - assert 10 - 1 - 5 = 4 with "basic chained math ##3 failed" + assert 5 - 3 - 1 = 1 with "basic chained math failed" + assert 5-3-2 = 0 with "basic chained math failed" + assert 10 - 1 - 5 = 4 with "basic chained math failed" - assert (9 - 1) - 3 = 5 with "basic chained math with parentheses ##1 failed" - assert 9 - (1 - 3) = 11 with "basic chained math with parentheses ##2 failed" + assert (9 - 1) - 3 = 5 with "basic chained math with parentheses failed" + assert 9 - (1 - 3) = 11 with "basic chained math with parentheses failed" diff --git a/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk index ca5b9b46d6e..c925f51c590 100644 --- a/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk +++ b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk @@ -1,6 +1,10 @@ test "spawn xp orb overwriting merged value" when running minecraft "1.14.4": # 1.13.2 seems not to merge xp orbs in the same way, so this test is skipped + # 1.21 also does not merge orbs, so this test is disabled. + # TODO: figure out how to force the orbs to merge on 1.21+ + running below minecraft "1.21" + # sanitize kill all xp orbs set {_spawn} to spawn of world "world" diff --git a/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk b/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk index cfa2aaa63e9..f0b63a43f23 100644 --- a/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk +++ b/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk @@ -8,8 +8,8 @@ test "double quote parsing": assert "Testing """ is set with "simple string with escaped quote failed" - assert "Testing %length of "abc"%" is set with "string with expression with string ##1 failed" - assert "%myFunction_five_nine_zero("Hello")% world" is "Hello world" with "string with expression with string ##2 failed" + assert "Testing %length of "abc"%" is set with "string with expression with string failed" + assert "%myFunction_five_nine_zero("Hello")% world" is "Hello world" with "string with expression with string failed" assert {_abc} is not set with "simple variable failed" diff --git a/src/test/skript/tests/regressions/6811-inventory-holder-location-doublechest.sk b/src/test/skript/tests/regressions/6811-inventory-holder-location-doublechest.sk new file mode 100644 index 00000000000..f46d4b52794 --- /dev/null +++ b/src/test/skript/tests/regressions/6811-inventory-holder-location-doublechest.sk @@ -0,0 +1,20 @@ +test "inventory holder location double chest": + set {_b::1} to the block at spawn of world "world" + set {_b::2} to the block north of {_b::1} + set {_prev::1} to type of block at {_b::1} + set {_prev::2} to type of block at {_b::2} + + set block at {_b::1} to chest[facing=east;type=right] + set block at {_b::2} to chest[facing=east;type=left] + + set {_inv} to inventory of {_b::1} + set {_holder} to holder of {_inv} + + set {_a-loc} to location of {_holder} + set {_b-loc::*} to location of {_b::1} and location of {_b::2} + + # clean up first in case assert fails + set block at {_b::1} to {_prev::1} + set block at {_b::2} to {_prev::2} + + assert {_b-loc::*} contains {_a-loc} with "holder location of double chest differs from block location" diff --git a/src/test/skript/tests/regressions/6830-remove air from slot.sk b/src/test/skript/tests/regressions/6830-remove air from slot.sk new file mode 100644 index 00000000000..13eaa29b1d3 --- /dev/null +++ b/src/test/skript/tests/regressions/6830-remove air from slot.sk @@ -0,0 +1,4 @@ +test "remove air from air slot": + set {_chest} to chest inventory with 3 rows + # throws exception if not fixed + remove 1 of (slot 0 of {_chest}) from (slot 0 of {_chest}) diff --git a/src/test/skript/tests/regressions/6843-current section reference issue.sk b/src/test/skript/tests/regressions/6843-current section reference issue.sk new file mode 100644 index 00000000000..20e8beb20ef --- /dev/null +++ b/src/test/skript/tests/regressions/6843-current section reference issue.sk @@ -0,0 +1,9 @@ +test "outdated current section references": + parse: + loop {_list::*}: + spawn a sheep at {_loc}: + set {_e} to event-entity + loop {_list::*}: + set {_var} to loop-value + set {_var} to loop-value-1 + assert last parse logs is not set with "loop-value and loop-value-1 should've worked" diff --git a/src/test/skript/tests/regressions/6881-sorted indices with children.sk b/src/test/skript/tests/regressions/6881-sorted indices with children.sk new file mode 100644 index 00000000000..3b7826e90d5 --- /dev/null +++ b/src/test/skript/tests/regressions/6881-sorted indices with children.sk @@ -0,0 +1,15 @@ +test "sorted indices with children": + set {_test::1} to 111 + set {_test::2} to 555 + set {_test::3} to 444 + set {_test::1::a} to 2 + set {_test::2::b} to 3 + set {_test::3::c} to 6 + set {_test::3::a::foo} to "i" + set {_test::3::b::bar} to "love" + set {_test::3::c::baz} to "skript" + set {_indices::*} to (sorted indices of {_test::*} in ascending order) + + assert {_indices::*} is ("1", "3", "2") with "sorted indices on list with children threw or was incorrect" + assert {_test::*} is (111, 555, 444) with "modified children wrongly" + assert {_test::3::*} is 6 with "modified children wrongly" diff --git a/src/test/skript/tests/regressions/6907-playsound entity method doesn't exist.sk b/src/test/skript/tests/regressions/6907-playsound entity method doesn't exist.sk new file mode 100644 index 00000000000..8c14277c5b3 --- /dev/null +++ b/src/test/skript/tests/regressions/6907-playsound entity method doesn't exist.sk @@ -0,0 +1,3 @@ +test "1.17 playsound exception": + # would throw exception on <1.17 + play sound "entity.experience_orb.pickup" with volume 1 at spawn of world "world" diff --git a/src/test/skript/tests/regressions/6908-timespan too big.sk b/src/test/skript/tests/regressions/6908-timespan too big.sk new file mode 100644 index 00000000000..82277775d68 --- /dev/null +++ b/src/test/skript/tests/regressions/6908-timespan too big.sk @@ -0,0 +1,6 @@ +test "large timespans truncated to ints": + set {_now} to now + set {_a} to unix timestamp of {_now} * 1 seconds + set {_b} to unix timestamp of 10 minutes from {_now} * 1 second + assert {_b} - {_a} is 10 minutes with "large timespan was truncated" + diff --git a/src/test/skript/tests/regressions/837-getting a uuid of a var inside code.sk b/src/test/skript/tests/regressions/837-getting a uuid of a var inside code.sk index 1da43a1f75d..e05e22b9639 100644 --- a/src/test/skript/tests/regressions/837-getting a uuid of a var inside code.sk +++ b/src/test/skript/tests/regressions/837-getting a uuid of a var inside code.sk @@ -2,8 +2,8 @@ test "variable parsing": assert {_test::%{_x}%} is not set with "simple list index using local variable failed" assert {_test::{_x}} is not set to fail with "list index with local variable without percentage signs failed" - assert {_test::%uuid of {_x}%} is not set with "list index with expression and local variable ##1 failed" - assert {_test::%{_x}'s uuid%} is not set with "list index with expression and local variable ##2 failed" + assert {_test::%uuid of {_x}%} is not set with "list index with expression and local variable failed" + assert {_test::%{_x}'s uuid%} is not set with "list index with expression and local variable failed" assert {_test::%{_a} split at {_b}%} is not set with "list index with expression with local variables on both ends failed" diff --git a/src/test/skript/tests/regressions/pull-6687-enchantment-update.sk b/src/test/skript/tests/regressions/pull-6687-enchantment-update.sk new file mode 100644 index 00000000000..94f4d9a3c42 --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6687-enchantment-update.sk @@ -0,0 +1,11 @@ +test "Enchantment Registry Update": + set {_i} to diamond sword of unbreaking 3 and sharpness 10 + + assert enchantment level of unbreaking of {_i} = 3 with "Unbreaking enchant on {_i} should have been 3" + assert enchantment level of sharpness of {_i} = 10 with "Sharpness enchant on {_i} should have been 10" + + set enchantment level of lure of {_i} to 5 + assert enchantment level of lure of {_i} = 5 with "Lure enchant on {_i} should have been 5" + + set enchantment level of sharpness of {_i} to 5 + assert enchantment level of sharpness of {_i} = 5 with "Sharpness enchant on {_i} should have been 5" diff --git a/src/test/skript/tests/regressions/pull-6832-potioneffecttype-compare.sk b/src/test/skript/tests/regressions/pull-6832-potioneffecttype-compare.sk new file mode 100644 index 00000000000..f0243b8a85f --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6832-potioneffecttype-compare.sk @@ -0,0 +1,3 @@ +test "compare potion effect types": + assert nausea = nausea with "2 nausea potion effect types should match" + assert type of (potion effect of nausea for 1 minute) = nausea with "type of potion effect of nausea should equal nausea" diff --git a/src/test/skript/tests/syntaxes/conditions/CondContains.sk b/src/test/skript/tests/syntaxes/conditions/CondContains.sk index e076c90f83f..fcc247bb7c0 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondContains.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondContains.sk @@ -1,34 +1,34 @@ test "contains condition": # Strings - assert "abc" contains "b" with "simple string contains failed ##1" - assert "abc" contains "abc" with "simple string contains failed ##2" - assert "abc" and "cde" contain "c" with "simple string contains failed ##3" - assert "abc" does not contain "d" with "simple string contains failed ##4" - assert "abc" and "def" do not contain "ghi" with "simple string contains failed ##5" - assert "abc" and "def" does not contain "ab" with "string object failed ##6" + assert "abc" contains "b" with "simple string contains failed" + assert "abc" contains "abc" with "simple string contains failed" + assert "abc" and "cde" contain "c" with "simple string contains failed" + assert "abc" does not contain "d" with "simple string contains failed" + assert "abc" and "def" do not contain "ghi" with "simple string contains failed" + assert "abc" and "def" does not contain "ab" with "string object failed" # Objects - assert "abc" and "def" contains "abc" with "object contains failed ##1" - assert 123 and 456 contains 456 with "object contains failed ##2" - assert 123 and 456 does not contain 789 with "object contains failed ##3" - assert 123 contains 123 with "object contains failed ##4" + assert "abc" and "def" contains "abc" with "object contains failed" + assert 123 and 456 contains 456 with "object contains failed" + assert 123 and 456 does not contain 789 with "object contains failed" + assert 123 contains 123 with "object contains failed" set {_l::*} to 5 times - assert {_l::*} contains 5 with "object contains failed ##5" - assert {_l::*} does not contain 6 with "object contains failed ##6" + assert {_l::*} contains 5 with "object contains failed" + assert {_l::*} does not contain 6 with "object contains failed" # Inventory set {_inv} to chest inventory with 3 rows set slot 0 of {_inv} to stone named "pp" set slot 1 of {_inv} to dirt - assert {_inv} contains stone with "inventory contains failed ##1" - assert {_inv} contains stone named "pp" with "inventory contains failed ##2" - assert {_inv} does not contain stone named "abc" with "inventory contains failed ##3" - assert {_inv} does not contain sand with "inventory contains failed ##4" - assert {_inv} contains dirt with "inventory contains failed ##5" - assert {_inv} does not contain 2 dirt with "inventory contains failed ##6" + assert {_inv} contains stone with "inventory contains failed" + assert {_inv} contains stone named "pp" with "inventory contains failed" + assert {_inv} does not contain stone named "abc" with "inventory contains failed" + assert {_inv} does not contain sand with "inventory contains failed" + assert {_inv} contains dirt with "inventory contains failed" + assert {_inv} does not contain 2 dirt with "inventory contains failed" set slot 2 of {_inv} to dirt - assert {_inv} contains 2 dirt with "inventory contains failed ##7" - assert {_inv} does not contain 3 dirt with "inventory contains failed ##8" + assert {_inv} contains 2 dirt with "inventory contains failed" + assert {_inv} does not contain 3 dirt with "inventory contains failed" set {_inv1} to chest inventory with 3 rows set {_inv2} to chest inventory with 3 rows diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsTamed.sk b/src/test/skript/tests/syntaxes/conditions/CondIsTamed.sk new file mode 100644 index 00000000000..82f092e5cc4 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsTamed.sk @@ -0,0 +1,21 @@ +test "is tamed": + spawn a horse at (spawn of world "world"): + set {_e} to entity + spawn an adult zombie at (spawn of world "world"): + set {_z} to entity + + assert {_e} is not tamed with "a normally spawned horse should not be tamed" + + tame {_e} + assert {_e} is tamed with "taming a horse should do exactly that" + + untame {_e} + assert {_e} is not tamed with "untaming a horse should do exactly that" + + tame {_} + tame {_z} + assert {_} is not tamed with "taming null should do nothing" + assert {_z} is not tamed with "taming an invalid entity should do nothing" + + delete entity within {_e} + delete entity within {_z} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk index a0c4339f556..b432c72f0ff 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk @@ -2,32 +2,32 @@ test "within condition" when running minecraft "1.17": # two locations set {_loc1} to location(0, 0, 0, "world") set {_loc2} to location(20, 20, 20, "world") - assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs ##1" - assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs ##2" + assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs" + assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs" # chunks set {_chunk1} to chunk at {_loc1} - assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk ##1" - assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk ##2" + assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk" + assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk" # worlds - assert location(10, 10, 10, "world") is within world("world") with "failed within world ##1" + assert location(10, 10, 10, "world") is within world("world") with "failed within world" # blocks set block at {_loc1} to stone - assert {_loc1} is within block at {_loc1} with "failed within block ##1" - assert {_loc2} is not within block at {_loc1} with "failed within block ##2" + assert {_loc1} is within block at {_loc1} with "failed within block" + assert {_loc2} is not within block at {_loc1} with "failed within block" # special case, non-full blocks set block at {_loc1} to lime carpet - assert {_loc1} is within block at {_loc1} with "failed within block ##3" - assert ({_loc1} ~ vector(0,0.3,0)) is not within block at {_loc1} with "failed within block ##4" + assert {_loc1} is within block at {_loc1} with "failed within block" + assert ({_loc1} ~ vector(0,0.3,0)) is not within block at {_loc1} with "failed within block" # entities set {_loc} to spawn of world "world" spawn a pig at {_loc} set {_pig} to last spawned entity - assert {_loc} is within {_pig} with "failed within entity ##1" - assert {_loc1} is not within {_pig} with "failed within entity ##2" + assert {_loc} is within {_pig} with "failed within entity" + assert {_loc1} is not within {_pig} with "failed within entity" delete random entity of {_pig} @@ -35,22 +35,22 @@ test "within condition" when running below minecraft "1.17": # two locations set {_loc1} to location(0, 0, 0, "world") set {_loc2} to location(20, 20, 20, "world") - assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs ##1" - assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs ##2" + assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs" + assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs" # chunks set {_chunk1} to chunk at {_loc1} - assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk ##1" - assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk ##2" + assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk" + assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk" # worlds - assert location(10, 10, 10, "world") is within world("world") with "failed within world ##1" + assert location(10, 10, 10, "world") is within world("world") with "failed within world" # entities set {_loc} to spawn of world "world" spawn a pig at {_loc} set {_pig} to last spawned entity - assert {_loc} is within {_pig} with "failed within entity ##1" - assert {_loc1} is not within {_pig} with "failed within entity ##2" + assert {_loc} is within {_pig} with "failed within entity" + assert {_loc1} is not within {_pig} with "failed within entity" delete random entity of {_pig} diff --git a/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk b/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk index e34bac7daac..538f8f5be04 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk @@ -1,10 +1,10 @@ test "method exists": - assert method "java.lang.String##length()" exists with "Existing method was not found" - assert method "java.lang.String##notReal()" doesn't exist with "Fake method was found" - assert method "java.lang.SuperFakeClass##notReal()" doesn't exist with "Fake class was found" - assert method "java.lang.String##indexOf(java.lang.String)" exists with "Existing method with parameter was not found" - assert method "java.lang.String##replace(java.lang.CharSequence,java.lang.CharSequence)" exists with "Existing method with multiple parameters not found" - assert method "java.lang.String##charAt(int)" exists with "Existing method with primitive parameter not found" - assert method "java.lang.String##substring(int,int)" exists with "Existing method with multiple primitive parameters not found" - assert method "java.lang.String##valueOf(char[])" exists with "Existing method with primitive array parameter not found" - assert method "java.lang.Runtime##exec(java.lang.String[],java.lang.String[])" exists with "Existing method with array parameter not found" + assert method "java.lang.String#length()" exists with "Existing method was not found" + assert method "java.lang.String#notReal()" doesn't exist with "Fake method was found" + assert method "java.lang.SuperFakeClass#notReal()" doesn't exist with "Fake class was found" + assert method "java.lang.String#indexOf(java.lang.String)" exists with "Existing method with parameter was not found" + assert method "java.lang.String#replace(java.lang.CharSequence,java.lang.CharSequence)" exists with "Existing method with multiple parameters not found" + assert method "java.lang.String#charAt(int)" exists with "Existing method with primitive parameter not found" + assert method "java.lang.String#substring(int,int)" exists with "Existing method with multiple primitive parameters not found" + assert method "java.lang.String#valueOf(char[])" exists with "Existing method with primitive array parameter not found" + assert method "java.lang.Runtime#exec(java.lang.String[],java.lang.String[])" exists with "Existing method with array parameter not found" diff --git a/src/test/skript/tests/syntaxes/conditions/CondTooltip.sk b/src/test/skript/tests/syntaxes/conditions/CondTooltip.sk new file mode 100644 index 00000000000..117a2b5bbb1 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondTooltip.sk @@ -0,0 +1,15 @@ +test "tooltip" when running minecraft "1.20.5": + + set {_item} to a diamond + assert entire tooltip of {_item} is shown with "item unexpectedly doesn't have entire tooltip" + assert additional tooltip of {_item} is shown with "item unexpectedly doesn't have additional tooltip" + + hide entire tooltip of {_item} + assert entire tooltip of {_item} is hidden with "item unexpectedly has entire tooltip" + hide additional tooltip of {_item} + assert additional tooltip of {_item} is hidden with "item unexpectedly has additional tooltip" + + show entire tooltip of {_item} + assert entire tooltip of {_item} is shown with "item unexpectedly doesn't have entire tooltip" + show additional tooltip of {_item} + assert additional tooltip of {_item} is shown with "item unexpectedly doesn't have additional tooltip" diff --git a/src/test/skript/tests/syntaxes/effects/EffContinue.sk b/src/test/skript/tests/syntaxes/effects/EffContinue.sk index 83804bc908f..2400e7441e0 100644 --- a/src/test/skript/tests/syntaxes/effects/EffContinue.sk +++ b/src/test/skript/tests/syntaxes/effects/EffContinue.sk @@ -11,9 +11,9 @@ test "continue effect": assert {_i} is not 5 with "continue in while failed" loop integers from 1 to 10: continue this loop if loop-value is 5 - assert loop-value is not 5 with "leveled continue failed ##1" + assert loop-value is not 5 with "leveled continue failed" loop integers from 11 to 20: continue 2nd loop if loop-value-2 is 15 - assert loop-value-2 is not 15 with "leveled continue failed ##2" + assert loop-value-2 is not 15 with "leveled continue failed" continue 1st loop if loop-value-1 is 10 - assert loop-value is not 10 with "leveled continue failed ##3" + assert loop-value is not 10 with "leveled continue failed" diff --git a/src/test/skript/tests/syntaxes/effects/EffEquip.sk b/src/test/skript/tests/syntaxes/effects/EffEquip.sk new file mode 100644 index 00000000000..a4a88a94f28 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffEquip.sk @@ -0,0 +1,12 @@ +test "equip effect": + spawn a zombie at spawn of "world": + set {_entity} to event-entity + + equip {_entity} with a diamond chestplate + assert chestplate of {_entity} is a diamond chestplate with "entity was not wearing a diamond chestplate" + set chestplate of {_entity} to air + + equip {_entity} with a diamond chestplate named "Test" + assert chestplate of {_entity} is a diamond chestplate named "Test" with "entity was not wearing a named diamond chestplate" + + delete the entity within {_entity} diff --git a/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk index b43a5786755..d39b3e6988a 100644 --- a/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk +++ b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk @@ -10,7 +10,7 @@ test "glowing sign blocks" when running minecraft "1.17.1": set block at {_loc} to {_original block} test "glowing sign items" when running minecraft "1.17.1": - set {_sign} to sign + set {_sign} to floor sign assert {_sign} doesn't have glowing text with "Sign had glowing text erroneously (1)" make {_sign} have glowing text assert {_sign} has glowing text with "Sign had normal text erroneously" diff --git a/src/test/skript/tests/syntaxes/effects/EffHealth.sk b/src/test/skript/tests/syntaxes/effects/EffHealth.sk index 41ecf33904b..b77c566076b 100644 --- a/src/test/skript/tests/syntaxes/effects/EffHealth.sk +++ b/src/test/skript/tests/syntaxes/effects/EffHealth.sk @@ -11,13 +11,13 @@ test "health effect": set {_m} to last spawned cow assert health of {_m} is 5 with "default cow health failed" damage {_m} by 0.5 - assert health of {_m} is 4.5 with "damage cow ##1 failed" + assert health of {_m} is 4.5 with "damage cow failed" damage {_m} by 99 - assert health of {_m} is 0 with "damage cow ##2 failed" + assert health of {_m} is 0 with "damage cow failed" heal {_m} by 1 - assert health of {_m} is 1 with "heal cow ##1 failed" + assert health of {_m} is 1 with "heal cow failed" heal {_m} by 0.5 - assert health of {_m} is 1.5 with "heal cow ##2 failed" + assert health of {_m} is 1.5 with "heal cow failed" heal {_m} by 99 - assert health of {_m} is 5 with "heal cow ##3 failed" + assert health of {_m} is 5 with "heal cow failed" clear all entities diff --git a/src/test/skript/tests/syntaxes/effects/EffSort.sk b/src/test/skript/tests/syntaxes/effects/EffSort.sk new file mode 100644 index 00000000000..4d832cd61fa --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffSort.sk @@ -0,0 +1,47 @@ +test "sorting": + set {_numbers::*} to shuffled integers from 1 to 50 + sort {_numbers::*} + assert {_numbers::*} is integers from 1 to 50 with "improper sorting of numbers" + + set {_numbers::*} to shuffled integers from 1 to 5 + sort {_numbers::*} by input * 20 + 4 - 3 # linear transformations don't affect order + assert {_numbers::*} is integers from 1 to 5 with "improper custom sorting of numbers" + + set {_numbers::*} to shuffled integers from 1 to 5 + set {_pre-sort-numbers::*} to {_numbers::*} + sort {_numbers::*} by "%input%" parsed as time # map expression returns null + assert {_numbers::*} is {_pre-sort-numbers::*} with "Invalid sorting expression adjusted list" + + set {_numbers::*} to shuffled integers from 1 to 5 + set {_pre-sort-numbers::*} to {_numbers::*} + sort {_numbers::*} by {_} + assert {_numbers::*} is {_pre-sort-numbers::*} with "Invalid sorting expression adjusted list" + + set {_numbers::*} to {_} + sort {_numbers::*} by input + 3 + assert {_numbers::*} is not set with "Invalid sorting of unset list" + + set {_chars::*} to shuffled characters between "a" and "f" + sort {_chars::*} + assert {_chars::*} is characters between "a" and "f" with "improper sorting of chars" + + set {_chars::*} to shuffled characters between "a" and "f" + sort {_chars::*} based on codepoint of input + assert {_chars::*} is characters between "a" and "f" with "improper custom sorting of chars" + + set {_mixed::*} to shuffled (characters between "a" and "f", integers from 1 to 5) + set {_pre-sort-mixed::*} to {_mixed::*} + sort {_mixed::*} + assert {_mixed::*} is {_pre-sort-mixed::*} with "incomparable mixed list was adjusted" + + set {_mixed::*} to shuffled (characters between "a" and "f", integers from 1 to 5) + sort {_mixed::*} by "%input%" + assert {_mixed::*} is 1, 2, 3, 4, 5, and characters between "a" and "f" with "improper custom sorting of mixed list" + + set {_list::x} to 1 + set {_list::aa} to 2 + set {_list::bxs} to 3 + set {_list::zysa} to 4 + set {_list::aaaaa} to 5 + sort {_list::*} by length of input index + assert {_list::*} is integers from 1 to 5 with "improper custom sorting based on index" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprAmountOfItems.sk b/src/test/skript/tests/syntaxes/expressions/ExprAmountOfItems.sk new file mode 100644 index 00000000000..9e40ff707ca --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprAmountOfItems.sk @@ -0,0 +1,14 @@ +test "amount of items": + set {_inventory} to a hopper inventory named "test" + assert the amount of stone in {_inventory} is 0 with "default amount failed" + add stone to {_inventory} + assert the amount of stone in {_inventory} is 1 with "single amount failed" + add stone named "bread" to {_inventory} + assert the amount of stone in {_inventory} is 2 with "different named items amount failed" + add 100 of iron ingot to {_inventory} + assert the amount of stone in {_inventory} is 2 with "add different item amount failed" + assert the amount of iron ingot in {_inventory} is 100 with "add 100 item amount failed" + remove stone from {_inventory} + assert the amount of stone in {_inventory} is 1 with "removed one amount failed" + remove stone from {_inventory} + assert the amount of stone in {_inventory} is 0 with "removed all amount failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk b/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk index 9b41359b7af..aa577e45d4c 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk @@ -4,15 +4,15 @@ test "arrows stuck": set arrows stuck in event-entity to 31 assert arrows stuck in event-entity is 31 with "arrows stuck set failed" add 5 to arrows stuck in event-entity - assert arrows stuck in event-entity is 36 with "arrows stuck add ##1 failed" + assert arrows stuck in event-entity is 36 with "arrows stuck add failed" remove 10 from arrows stuck in event-entity - assert arrows stuck in event-entity is 26 with "arrows stuck remove ##1 failed" + assert arrows stuck in event-entity is 26 with "arrows stuck remove failed" remove 999 from arrows stuck in event-entity - assert arrows stuck in event-entity is 0 with "arrows stuck remove ##2 failed" + assert arrows stuck in event-entity is 0 with "arrows stuck remove failed" remove -2 from arrows stuck in event-entity - assert arrows stuck in event-entity is 2 with "arrows stuck remove ##3 failed" + assert arrows stuck in event-entity is 2 with "arrows stuck remove failed" add -1 to arrows stuck in event-entity - assert arrows stuck in event-entity is 1 with "arrows stuck add ##2 failed" + assert arrows stuck in event-entity is 1 with "arrows stuck add failed" delete arrows stuck in event-entity assert arrows stuck in event-entity is 0 with "arrows stuck delete failed" delete event-entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk index 724000493e5..3935ad883b6 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk @@ -12,3 +12,18 @@ test "durability": set durability of {_i} to 0 assert damage of {_i} is {_max} with "max item damage failed" assert durability of {_i} is 0 with "zero item durability failed" + + # Test out of bound values + set durability of {_i} to 500 + assert damage of {_i} is 0 with "damage of item should be 0 when setting durability higher than max" + assert durability of {_i} is {_max} with "durability of item should be max when setting higher than max" + + set damage of {_i} to -1 + assert damage of {_i} is 0 with "damage of item should be 0 when setting damage less than 0" + assert durability of {_i} is {_max} with "durability of item should be max when setting damage below 0" + +test "durability - custom" when running minecraft "1.20.5": + set {_i} to 1 of iron sword + set max durability of {_i} to 1000 + damage {_i} by 1 + assert durability of {_i} = 999 with "durability of iron sword should be 999" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk index 730afc9d164..ed04483dbe0 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk @@ -6,9 +6,9 @@ test "attributes 1": add 5 to movement speed attribute of event-entity assert movement speed attribute of event-entity is 8.14 with "attribute add failed" remove 4 from movement speed attribute of event-entity - assert movement speed attribute of event-entity is 4.14 with "attribute remove ##1 failed" + assert movement speed attribute of event-entity is 4.14 with "attribute remove failed" remove 10 from movement speed attribute of event-entity - assert movement speed attribute of event-entity is -5.86 with "attribute remove ##2 failed" # Negative attribute values should be safe + assert movement speed attribute of event-entity is -5.86 with "attribute remove failed" # Negative attribute values should be safe delete movement speed attribute of event-entity assert movement speed attribute of event-entity is 0 with "attribute delete failed" delete event-entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk index b1f1738f20d..bc195b894c8 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk @@ -3,4 +3,15 @@ test "where filter": assert first element of ({_list::*} where [string input is "foo"]) is "foo" with "ExprFilter filtered incorrectly" assert {_list::*} where [number input is set] is not set with "ExprFilter provided input value when classinfo did not match" assert first element of ({_list::*} where [input is "foo"]) is "foo" with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "foobar"]) is "foobar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foobar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foo" or "bar"]) is 2 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is set]) is 3 with "ExprFilter filtered object input incorrectly" assert {_list::*} where [false is true] is not set with "ExprFilter returned objects with false condition" + assert ({_list::*} where [input is (("foo" and "bar") where [input is "bar"])]) is "bar" with "Failed filter with filter within condition" + assert (({_list::*} where [input is "foo"]) where [input is "foo"]) is "foo" with "Failed chained filters" + assert {_list::*} where [input index is "2" or "3"] is "bar" and "foobar" with "Failed input index filter" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk index c6e47ec617e..7caec6fcb3f 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk @@ -4,14 +4,14 @@ test "freeze time" when running minecraft "1.18": set freeze time of entity to 3 seconds assert freeze time of entity is 3 seconds with "freeze time set failed" add 2 seconds to freeze time of entity - assert freeze time of entity is 5 seconds with "freeze time add ##1 failed" + assert freeze time of entity is 5 seconds with "freeze time add failed" add 10 seconds to freeze time of entity - assert freeze time of entity is 15 seconds with "freeze time add ##2 failed" # freeze time should not be capped at entity's max freeze time (7 seconds for a cow) + assert freeze time of entity is 15 seconds with "freeze time add failed" # freeze time should not be capped at entity's max freeze time (7 seconds for a cow) remove 6 seconds from freeze time of entity - assert freeze time of entity is 9 seconds with "freeze time remove ##1 failed" + assert freeze time of entity is 9 seconds with "freeze time remove failed" remove 10 seconds from freeze time of entity - assert freeze time of entity is 0 seconds with "freeze time remove ##2 failed" # freeze time should not be negative + assert freeze time of entity is 0 seconds with "freeze time remove failed" # freeze time should not be negative delete freeze time of entity assert freeze time of entity is 0 seconds with "freeze time delete failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprItemWithCustomModelData.sk b/src/test/skript/tests/syntaxes/expressions/ExprItemWithCustomModelData.sk new file mode 100644 index 00000000000..098cda9abee --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprItemWithCustomModelData.sk @@ -0,0 +1,13 @@ +test "item with custom model data" when minecraft version is not "1.13.2": + set {_i} to stone + assert the custom model data of {_i} is 0 with "default model data failed" + set {_i} to stone with custom model data 5 + assert the custom model data of {_i} is 5 with "simple model data set failed" + set {_i} to stone with custom model data -1 + assert the custom model data of {_i} is -1 with "negative model data set failed" + set {_i} to {_i} with custom model data 2 + assert the custom model data of {_i} is 2 with "existing item model data set failed" + set {_i} to {_i} with custom model data 3.3 + assert the custom model data of {_i} is 3 with "decimal item model data set failed" + set {_i} to {_i} with custom model data 3.999 + assert the custom model data of {_i} is 3 with "close decimal item model data set failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk b/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk index 9b2c90fb25f..ce5253d40b0 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk @@ -1,3 +1,29 @@ + +test "items in (inventory)": + set {_inventory} to a hopper inventory named "test" + add stone to {_inventory} + add stone named "bread" to {_inventory} + add 100 of iron ingot to {_inventory} + loop items in {_inventory}: + if loop-value is stone: + continue + else if loop-value is iron ingot: + continue + else: + assert true is false with "unexpected item in the inventory area: %loop-value%" + set {_list::*} to items in {_inventory} + assert size of {_list::*} is 4 with "size of items in failed" + assert {_list::1} is stone with "first item failed" + assert {_list::2} is stone named "bread" with "second item failed" + assert {_list::3} is 64 of iron ingot with "third item failed" + assert {_list::4} is 36 of iron ingot with "split fourth item failed" + remove stone from {_inventory} + set {_list::*} to items in {_inventory} + assert size of {_list::*} is 3 with "size of second items in failed" + assert {_list::1} is stone named "bread" with "new first item failed" + assert {_list::2} is 64 of iron ingot with "new second item failed" + assert {_list::3} is 36 of iron ingot with "new third item failed" + test "filtering ExprItemsIn": set {_world} to random world out of all worlds set block at spawn of {_world} to chest @@ -5,8 +31,8 @@ test "filtering ExprItemsIn": set slot 1 of {_inv} to dirt set slot 2 of {_inv} to stone set slot 3 of {_inv} to bucket - assert all blocks in inventory {_inv} are dirt or stone with "found correct items with ExprItemsIn##get" - assert (all blocks in inventory {_inv} where [true is true]) are dirt or stone with "found correct items with ExprItemsIn##iterator" + assert all blocks in inventory {_inv} are dirt or stone with "found correct items with ExprItemsIn#get" + assert (all blocks in inventory {_inv} where [true is true]) are dirt or stone with "found correct items with ExprItemsIn#iterator" set {_dirt} to dirt assert all {_dirt} in inventory {_inv} is dirt with "found incorrect items with variable itemtypes" @@ -17,5 +43,5 @@ test "unfiltered ExprItemsIn": set slot 1 of {_inv} to dirt set slot 2 of {_inv} to stone set slot 3 of {_inv} to bucket - assert all items in inventory {_inv} are dirt, stone or bucket with "found correct items with ExprItemsIn##get" - assert (all items in inventory {_inv} where [true is true]) are dirt, stone or bucket with "found correct items with ExprItemsIn##iterator" + assert all items in inventory {_inv} are dirt, stone or bucket with "found correct items with ExprItemsIn#get" + assert (all items in inventory {_inv} where [true is true]) are dirt, stone or bucket with "found correct items with ExprItemsIn#iterator" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprJoinSplit.sk b/src/test/skript/tests/syntaxes/expressions/ExprJoinSplit.sk new file mode 100644 index 00000000000..2f5f5cd837a --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprJoinSplit.sk @@ -0,0 +1,27 @@ +test "join strings": + assert join "a", "b", "c" is "abc" with "join strings without delimiter failed" + assert join "a", "b", "c" with " " is "a b c" with "join strings with delimiter failed" + +test "split strings on string": + assert split "abc" at "[a-z]" is "abc" with "split with regex chars failed" + + assert split "a b c" at " " is "a", "b", "c" with "split failed" + assert split "a b c" at " " with case sensitivity is "a", "b", "c" with "split with case sensitivity failed" + assert split "a b c" at " " without trailing string is "a", "b", "c" with "split without trailing string failed" + + assert split "abc" at "" is "a", "b", "c", "" with "split with empty delimiter failed" + assert split "abc" at "" with case sensitivity is "a", "b", "c", "" with "split with empty delimiter with case sensitivity failed" + assert split "abc" at "" without trailing string is "a", "b", "c" with "split with empty delimiter without trailing string failed" + assert split "abc" at "" with case sensitivity without trailing string is "a", "b", "c" with "split with empty delimiter with case sensitivity without trailing string failed" + + assert split "-x-X" at "x" is "-", "-", "" with "split with delimiter at end failed" + assert split "-x-X" at "x" with case sensitivity is "-", "-X" with "split with delimiter at end with case sensitivity failed" + assert split "-x-X" at "x" without trailing string is "-", "-" with "split with delimiter at end without trailing string failed" + assert split "-x-X" at "x" with case sensitivity without trailing string is "-", "-X" with "split with delimiter at end with case sensitivity without trailing string failed" + +test "split strings on regex": + assert regex split "a b c" at " " is "a", "b", "c" with "regex split failed" + assert regex split "a b_c" at "( |_)" is "a", "b", "c" with "regex split with regex failed" + + assert regex split "-a-b-c" at "[a-z]" is "-", "-", "-", "" with "regex split with delimiter at end failed" + assert regex split "-a-b-c" at "[a-z]" without trailing string is "-", "-", "-" with "regex split with delimiter at end without trailing string failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLastDamage.sk b/src/test/skript/tests/syntaxes/expressions/ExprLastDamage.sk new file mode 100644 index 00000000000..0ed3f3f3e4c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLastDamage.sk @@ -0,0 +1,15 @@ +test "last damage": + set {_l} to location 0.5 above highest block at location(1,1,1) + spawn a sheep at {_l} + set {_e} to last spawned entity + + assert last damage of {_e} = 0 with "last damage of newly spawned entity should be 0" + + damage {_e} by 1 + assert last damage of {_e} = 1 with "last damage of entity should be 1 after damaging it by 1" + + set last damage of {_e} to 3 + assert last damage of {_e} = 3 with "last damage of entity should be 3 after setting to 3" + + # thank you for your service + delete entity in {_e} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk new file mode 100644 index 00000000000..b0f90139f50 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk @@ -0,0 +1,60 @@ +test "lowest/highest solid block (old height)" when running below minecraft "1.18": + + # highest solid block + set {_oldBlock::1} to block data of block at location(0.5, 255.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 254.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 253.5, 0.5, "world") + set block at location(0.5, 255.5, 0.5, "world") to air + set block at location(0.5, 254.5, 0.5, "world") to air + set block at location(0.5, 253.5, 0.5, "world") to dirt + set {_highest} to highest solid block at location(0.5, 64, 0.5, "world") + assert type of {_highest} is dirt with "highest block is not dirt (got '%type of {_highest}%')" + assert location of {_highest} is location(0.5, 253.5, 0.5, "world") with "highest block is not at 0.5,253.5,0.5 (got '%location of {_highest}%')" + set block at location(0.5, 255.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 254.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, 253.5, 0.5, "world") to {_oldBlock::3} + + # lowest solid block + set {_oldBlock::1} to block data of block at location(0.5, 0.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 1.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 2.5, 0.5, "world") + set block at location(0.5, 0.5, 0.5, "world") to air + set block at location(0.5, 1.5, 0.5, "world") to air + set block at location(0.5, 2.5, 0.5, "world") to dirt + set {_lowest} to lowest solid block at location(0.5, 64, 0.5, "world") + assert type of {_lowest} is dirt with "lowest block is not dirt (got '%type of {_lowest}%')" + assert location of {_lowest} is location(0.5, 2.5, 0.5, "world") with "lowest block is not at 0.5,2.5,0.5 (got '%location of {_lowest}%')" + set block at location(0.5, 0.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 1.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, 2.5, 0.5, "world") to {_oldBlock::3} + +test "lowest/highest solid block (new height)" when running minecraft "1.18": + + # highest solid block + set {_oldBlock::1} to block data of block at location(0.5, 319.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 318.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 317.5, 0.5, "world") + set block at location(0.5, 319.5, 0.5, "world") to air + set block at location(0.5, 318.5, 0.5, "world") to air + set block at location(0.5, 317.5, 0.5, "world") to dirt + set {_highest} to highest solid block at location(0.5, 64, 0.5, "world") + assert type of {_highest} is dirt with "highest block is not dirt (got '%type of {_highest}%')" + assert location of {_highest} is location(0.5, 317.5, 0.5, "world") with "highest block is not at 0.5,317.5,0.5 (got '%location of {_highest}%')" + set block at location(0.5, 319.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 318.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 317.5, 0.5, "world") to {_oldBlock::1} + + # lowest solid block + set {_oldBlock::1} to block data of block at location(0.5, -63.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, -62.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, -61.5, 0.5, "world") + set block at location(0.5, -63.5, 0.5, "world") to air + set block at location(0.5, -62.5, 0.5, "world") to air + set block at location(0.5, -61.5, 0.5, "world") to dirt + set {_lowest} to lowest solid block at location(0.5, 64, 0.5, "world") + assert type of {_lowest} is dirt with "lowest block is not dirt (got '%type of {_lowest}%')" + assert location of {_lowest} is location(0.5, -61.5, 0.5, "world") with "lowest block is not at 0.5,-61.5,0.5 (got '%location of {_lowest}%')" + set block at location(0.5, -63.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, -62.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, -61.5, 0.5, "world") to {_oldBlock::3} + diff --git a/src/test/skript/tests/syntaxes/expressions/ExprMaxDurability.sk b/src/test/skript/tests/syntaxes/expressions/ExprMaxDurability.sk index 0464f62d731..c41aee476c4 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprMaxDurability.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprMaxDurability.sk @@ -3,3 +3,13 @@ test "max durability": assert max durability of glass is 0 with "max durability of glass failed" assert max durability of shears is 238 with "max durability of shears failed" assert max durability of diamond sword is 1561 with "max durability of diamond sword failed" + +test "max durability - custom" when running minecraft "1.20.5": + set {_i} to 1 of iron sword + assert max durability of {_i} = 250 with "max durability of iron sword should be 250" + + set max durability of {_i} to 1000 + assert max durability of {_i} = 1000 with "adjusted max durability of iron sword should be 1000" + + reset max durability of {_i} + assert max durability of {_i} = 250 with "reset max durability of iron sword should be 250" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprNoDamageTicks.sk b/src/test/skript/tests/syntaxes/expressions/ExprNoDamageTicks.sk index 794dffa6e87..2d314c9e6d2 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprNoDamageTicks.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprNoDamageTicks.sk @@ -5,14 +5,14 @@ test "no damage ticks": set {_m}'s invulnerability ticks to 25 assert {_m}'s invulnerability ticks is 25 with "no damage ticks set failed" add 5 to {_m}'s invulnerability ticks - assert {_m}'s invulnerability ticks is 30 with "no damage ticks add ##1 failed" + assert {_m}'s invulnerability ticks is 30 with "no damage ticks add failed" remove 12 from {_m}'s invulnerability ticks - assert {_m}'s invulnerability ticks is 18 with "no damage ticks remove ##1 failed" + assert {_m}'s invulnerability ticks is 18 with "no damage ticks remove failed" remove 999 from {_m}'s invulnerability ticks - assert {_m}'s invulnerability ticks is 0 with "no damage ticks remove ##2 failed" + assert {_m}'s invulnerability ticks is 0 with "no damage ticks remove failed" remove -2 from {_m}'s invulnerability ticks - assert {_m}'s invulnerability ticks is 2 with "no damage ticks remove ##3 failed" + assert {_m}'s invulnerability ticks is 2 with "no damage ticks remove failed" add -1 to {_m}'s invulnerability ticks - assert {_m}'s invulnerability ticks is 1 with "no damage ticks add ##2 failed" + assert {_m}'s invulnerability ticks is 1 with "no damage ticks add failed" delete {_m}'s invulnerability ticks assert {_m}'s invulnerability ticks is 0 with "no damage ticks delete failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk b/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk index 7d919d788da..36b95564dc7 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk @@ -4,11 +4,11 @@ test "portal cooldown": set event-entity's portal cooldown to 25 ticks assert event-entity's portal cooldown is 25 ticks with "portal cooldown set failed" add 5 seconds to event-entity's portal cooldown - assert event-entity's portal cooldown is 125 ticks with "portal cooldown add ##1 failed" + assert event-entity's portal cooldown is 125 ticks with "portal cooldown add failed" remove 12 ticks from event-entity's portal cooldown - assert event-entity's portal cooldown is 113 ticks with "portal cooldown remove ##1 failed" + assert event-entity's portal cooldown is 113 ticks with "portal cooldown remove failed" remove 999 ticks from event-entity's portal cooldown - assert event-entity's portal cooldown is 0 ticks with "portal cooldown remove ##2 failed" + assert event-entity's portal cooldown is 0 ticks with "portal cooldown remove failed" delete event-entity's portal cooldown assert event-entity's portal cooldown is 0 ticks with "portal cooldown delete failed" reset event-entity's portal cooldown diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRemainingAir.sk b/src/test/skript/tests/syntaxes/expressions/ExprRemainingAir.sk new file mode 100644 index 00000000000..3f9f5041db7 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRemainingAir.sk @@ -0,0 +1,18 @@ +test "remaining air": + + spawn a pig at location of spawn of world "world": + set {_e} to event-entity + + reset remaining air of {_e} + assert remaining air of {_e} is 15 seconds with "resetting did not set to 15 seconds" + + set remaining air of {_e} to 3 years + assert remaining air of {_e} is 3 years with "setting to 3 years did not work" + + remove 5 years from remaining air of {_e} + assert remaining air of {_e} is 0 seconds with "removing did not limit to 0 seconds" + + add 10 years to remaining air of {_e} + assert ticks of remaining air of {_e} is 2147483647 with "adding did not limit to max int value" + + delete the entity within {_e} diff --git a/src/test/skript/tests/syntaxes/functions/caseEquals.sk b/src/test/skript/tests/syntaxes/functions/caseEquals.sk index f777e6ff7df..ace8276da6e 100644 --- a/src/test/skript/tests/syntaxes/functions/caseEquals.sk +++ b/src/test/skript/tests/syntaxes/functions/caseEquals.sk @@ -1,6 +1,6 @@ test "case equals function": - assert caseEquals("") is true with "case equals function failed ##1" - assert caseEquals("dummy") is true with "case equals function failed ##2" - assert caseEquals("hi", "Hi") is false with "case equals function failed ##3" - assert caseEquals("text", "text", "tExt") is false with "case equals function failed ##4" - assert caseEquals("🐢<-turtle", "🐢<-turtle", "🐢<-turtle", "🐢<-turtle") is true with "case equals function failed ##5" + assert caseEquals("") is true with "case equals function failed" + assert caseEquals("dummy") is true with "case equals function failed" + assert caseEquals("hi", "Hi") is false with "case equals function failed" + assert caseEquals("text", "text", "tExt") is false with "case equals function failed" + assert caseEquals("🐢<-turtle", "🐢<-turtle", "🐢<-turtle", "🐢<-turtle") is true with "case equals function failed" diff --git a/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk b/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk new file mode 100644 index 00000000000..b96d67a7487 --- /dev/null +++ b/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk @@ -0,0 +1,7 @@ +test "offline player function": + set {_lookup} to offlineplayer("Notch") + assert {_lookup} is set with "Failed to look up offline player" + +test "offline player function no lookup" when running minecraft "1.16": + set {_non-lookup} to offlineplayer("Dinnerbone", false) + assert {_non-lookup} is not set with "Looked up offline player when told not to" diff --git a/src/test/skript/tests/syntaxes/sections/SecConditional.sk b/src/test/skript/tests/syntaxes/sections/SecConditional.sk index f9991e9f621..4957319f369 100644 --- a/src/test/skript/tests/syntaxes/sections/SecConditional.sk +++ b/src/test/skript/tests/syntaxes/sections/SecConditional.sk @@ -18,18 +18,18 @@ test "SecConditional": delete {_b} 1 = 2 else if 1 = 1: - assert 1 = 2 with "conditional failed ##1" + assert 1 = 2 with "conditional failed" else: - assert 1 = 2 with "conditional failed ##2" + assert 1 = 2 with "conditional failed" if {_b} is set: - assert 1 = 2 with "conditional failed ##3" + assert 1 = 2 with "conditional failed" if 1 = 2: - assert 1 = 2 with "conditional failed ##4" + assert 1 = 2 with "conditional failed" else if 1 = 1: exit 1 section else: - assert 1 = 2 with "conditional failed ##5" + assert 1 = 2 with "conditional failed" test "SecConditional - if all true": if: