diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..560dcad5af7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +# .git-blame-ignore-revs + +# cleanup lang package - mostly renaming variables +98abf5d7773df11c65d66e4e9485d0f1e3ef8821 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce87d77a996..698a65a0554 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,6 +2,6 @@ --- -**Target Minecraft Versions:** -**Requirements:** -**Related Issues:** +**Target Minecraft Versions:** any +**Requirements:** none +**Related Issues:** none diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 36ddf386257..a0c87141c13 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -28,7 +28,7 @@ jobs: - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava17 - name: Upload Nightly Build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() with: name: skript-nightly diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 50b42dc0491..090e6b3db6a 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -16,9 +16,9 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' @@ -28,7 +28,7 @@ jobs: - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava8 - name: Upload Nightly Build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() with: name: skript-nightly diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 64cd93ec47f..ac8de249e1f 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -16,9 +16,9 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index cafa1c0f501..2a9bf589e67 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -16,9 +16,9 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 06cb5569076..f6f7ebc8a27 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -37,7 +37,6 @@ jobs: - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Release Docs Bot git_email: releasedocs@skriptlang.org @@ -69,6 +68,7 @@ jobs: uses: ./skript/.github/workflows/docs/generate-docs with: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true - name: Push archive documentation diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 2499cf67f74..77b8a55fd4e 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -12,9 +12,9 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.gitignore b/.gitignore index fbddca8fd46..47df9cbc0de 100755 --- a/.gitignore +++ b/.gitignore @@ -223,3 +223,6 @@ gradle-app.setting # TODO remove this in the future after some time since https://github.com/SkriptLang/Skript/pull/4979 # This ensures developers don't upload their old existing test_runners/ folder. test_runners/ + +## Mac MetaData +**/.DS_Store diff --git a/build.gradle b/build.gradle index 15505bcbd3e..92f13c47f46 100644 --- a/build.gradle +++ b/build.gradle @@ -27,20 +27,20 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.4-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' - implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { + implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.3', { exclude group: 'org.bstats', module: 'bstats-bukkit' } implementation fileTree(dir: 'lib', include: '*.jar') testShadow group: 'junit', name: 'junit', version: '4.13.2' - testShadow group: 'org.easymock', name: 'easymock', version: '5.2.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.0.1' } task checkAliases { @@ -150,7 +150,7 @@ license { } task releaseJavadoc(type: Javadoc) { - title = project.property('version') + title = project.name + ' ' + project.property('version') source = sourceSets.main.allJava classpath = configurations.compileClasspath options.encoding = 'UTF-8' @@ -186,7 +186,10 @@ enum Modifiers { } // Create a test task with given name, environments dir/file, dev mode and java version. -void createTestTask(String name, String desc, String environments, int javaVersion, Modifiers... modifiers) { +// -1 on the timeout means it'll be disabled. +void createTestTask(String name, String desc, String environments, int javaVersion, long timeout, Modifiers... modifiers) { + if (timeout == 0) + timeout = 300000 // 5 minutes boolean junit = modifiers.contains(Modifiers.JUNIT) boolean releaseDocs = modifiers.contains(Modifiers.GEN_RELEASE_DOCS) boolean docs = modifiers.contains(Modifiers.GEN_NIGHTLY_DOCS) || releaseDocs @@ -223,14 +226,15 @@ void createTestTask(String name, String desc, String environments, int javaVersi main = 'ch.njol.skript.test.platform.PlatformMain' args = [ 'build/test_runners', - junit ? 'src/test/skript/tests/junit' : 'src/test/skript/tests', + junit ? 'src/test/skript/junit' : 'src/test/skript/tests', 'src/test/resources/runner_data', environments, modifiers.contains(Modifiers.DEV_MODE), docs, junit, modifiers.contains(Modifiers.DEBUG), - project.findProperty('verbosity') ?: "null" + project.findProperty('verbosity') ?: "null", + timeout ] // Do first is used when throwing exceptions. @@ -246,7 +250,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.1.json' +def latestEnv = 'java17/paper-1.20.4.json' def latestJava = 17 def oldestJava = 8 @@ -265,21 +269,21 @@ compileTestJava.options.encoding = 'UTF-8' String environments = 'src/test/skript/environments/'; String env = project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json' int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Integer.parseInt(project.property('testEnvJavaVersion') as String) -createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava) -createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava) -createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava) -createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, Modifiers.DEV_MODE, Modifiers.DEBUG) -createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, Modifiers.PROFILE) -createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, Modifiers.GEN_NIGHTLY_DOCS) -createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, Modifiers.GEN_RELEASE_DOCS) +createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0) +createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava, 0) +createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava, 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 skriptTestJava8, skriptTestJava17 } -createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, Modifiers.JUNIT) -createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, Modifiers.JUNIT) -createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, Modifiers.JUNIT) +createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0, Modifiers.JUNIT) +createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, 0, Modifiers.JUNIT) +createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' dependsOn JUnitJava8, JUnitJava17 @@ -291,10 +295,8 @@ task githubResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('alpha')) - channel = 'alpha' - else if (version.contains('beta')) - channel = 'beta' + if (version.contains('pre')) + channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, 'today' : '' + LocalTime.now(), @@ -326,10 +328,8 @@ task spigotResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('alpha')) - channel = 'alpha' - else if (version.contains('beta')) - channel = 'beta' + if (version.contains('pre')) + channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, 'today' : '' + LocalTime.now(), @@ -365,7 +365,7 @@ task nightlyResources(type: ProcessResources) { 'version' : version, 'today' : '' + LocalTime.now(), 'release-flavor' : 'skriptlang-nightly', // SkriptLang build, automatically done by CI - 'release-channel' : 'alpha', // No update checking, but these are VERY unstable + 'release-channel' : 'prerelease', // No update checking, but these are VERY unstable 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No autoupdates for now 'release-source' : '', 'release-download': 'null' @@ -389,7 +389,7 @@ task nightlyRelease(type: ShadowJar) { javadoc { dependsOn nightlyResources - + source = sourceSets.main.allJava exclude("ch/njol/skript/conditions/**") diff --git a/code-conventions.md b/code-conventions.md index 37ac182cd62..327951d2299 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -127,9 +127,11 @@ If we need to remove or alter contributed code due to a licensing issue we will - Static constant fields should be named in `UPPER_SNAKE_CASE` * Localised messages should be named in `lower_snake_case` - And that is the only place where snake_case is acceptable -* Use prefixes only where their use has been already estabilished (such as `ExprSomeRandomThing`) +* Use prefixes only where their use has been already established (such as `ExprSomeRandomThing`) - Otherwise, use postfixes where necessary - Common occurrences include: Struct (Structure), Sec (Section), EffSec (EffectSection), Eff (Effect), Cond (Condition), Expr (Expression) +* Ensure variable/field names are descriptive. Avoid using shorthand names like `e`, or `c`. + - e.g. Event should be `event`, not `e`. `e` is ambiguous and could mean a number of things. ### Comments * Prefer to comment *why* you're doing things instead of how you're doing them diff --git a/gradle.properties b/gradle.properties index 847cfde2fcc..3271da70815 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,11 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +# Ensure encoding is consistent across systems. +org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.0-dev +version=2.8.4 jarName=Skript.jar -testEnv=java17/paper-1.20.1 +testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 diff --git a/settings.gradle b/settings.gradle index e5432e8214f..78b185aca4c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } rootProject.name = 'Skript' diff --git a/skript-aliases b/skript-aliases index fb9c3044e55..490bbeadf6e 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit fb9c3044e555667b4dc5558467608bd55fa32df0 +Subproject commit 490bbeadf6e44e26dd436acfd191dae5b740ebe6 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9740ee27efa..8b7c00f6219 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -26,6 +26,7 @@ import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.classes.data.DefaultConverters; import ch.njol.skript.classes.data.DefaultFunctions; +import ch.njol.skript.classes.data.DefaultOperations; import ch.njol.skript.classes.data.JavaClasses; import ch.njol.skript.classes.data.SkriptClasses; import ch.njol.skript.command.Commands; @@ -82,6 +83,7 @@ import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; import com.google.common.collect.Lists; @@ -106,6 +108,7 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.skriptlang.skript.lang.comparator.Comparator; @@ -147,7 +150,6 @@ import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; @@ -534,6 +536,7 @@ public void onEnable() { new DefaultComparators(); new DefaultConverters(); new DefaultFunctions(); + new DefaultOperations(); ChatMessages.registerListeners(); @@ -591,6 +594,8 @@ public void run() { tainted = true; try { getAddonInstance().loadClasses("ch.njol.skript.test.runner"); + if (TestMode.JUNIT) + getAddonInstance().loadClasses("org.skriptlang.skript.test.junit.registration"); } catch (IOException e) { Skript.exception("Failed to load testing environment."); Bukkit.getServer().shutdown(); @@ -684,11 +689,13 @@ protected void afterErrors() { TestTracker.testFailed("exception was thrown during execution"); } if (TestMode.JUNIT) { - SkriptLogger.setVerbosity(Verbosity.DEBUG); info("Running all JUnit tests..."); long milliseconds = 0, tests = 0, fails = 0, ignored = 0, size = 0; try { List> classes = Lists.newArrayList(Utils.getClasses(Skript.getInstance(), "org.skriptlang.skript.test", "tests")); + // Don't attempt to run inner/anonymous classes as tests + classes.removeIf(Class::isAnonymousClass); + classes.removeIf(Class::isLocalClass); // Test that requires package access. This is only present when compiling with src/test. classes.add(Class.forName("ch.njol.skript.variables.FlatFileStorageTest")); size = classes.size(); @@ -702,6 +709,23 @@ protected void afterErrors() { Result junit = JUnitCore.runClasses(clazz); TestTracker.testStarted("JUnit: '" + test + "'"); + /** + * Usage of @After is pointless if the JUnit class requires delay. As the @After will happen instantly. + * The JUnit must override the 'cleanup' method to avoid Skript automatically cleaning up the test data. + */ + boolean overrides = false; + for (Method method : clazz.getDeclaredMethods()) { + if (!method.isAnnotationPresent(After.class)) + continue; + if (SkriptJUnitTest.getShutdownDelay() > 1) + warning("Using @After in JUnit classes, happens instantaneously, and JUnit class '" + test + "' requires a delay. Do your test cleanup in the script junit file or 'cleanup' method."); + if (method.getName().equals("cleanup")) + overrides = true; + } + if (SkriptJUnitTest.getShutdownDelay() > 1 && !overrides) + error("The JUnit class '" + test + "' does not override the method 'cleanup' thus the test data will instantly be cleaned up. " + + "This JUnit test requires longer shutdown time: " + SkriptJUnitTest.getShutdownDelay()); + // Collect all data from the current JUnit test. shutdownDelay = Math.max(shutdownDelay, SkriptJUnitTest.getShutdownDelay()); tests += junit.getRunCount(); @@ -712,7 +736,7 @@ protected void afterErrors() { // If JUnit failures are present, add them to the TestTracker. junit.getFailures().forEach(failure -> { String message = failure.getMessage() == null ? "" : " " + failure.getMessage(); - TestTracker.testFailed("'" + test + "': " + message); + TestTracker.JUnitTestFailed(test, message); Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); }); SkriptJUnitTest.clearJUnitTest(); @@ -734,7 +758,7 @@ protected void afterErrors() { // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests Bukkit.getScheduler().runTaskLater(Skript.this, () -> { if (TestMode.JUNIT && !EffObjectives.isJUnitComplete()) - TestTracker.testFailed(EffObjectives.getFailedObjectivesString()); + EffObjectives.fail(); info("Collecting results to " + TestMode.RESULTS_FILE); String results = new Gson().toJson(TestTracker.collectResults()); @@ -1133,7 +1157,15 @@ public void onPluginDisable(PluginDisableEvent event) { try { // Spigot removed the mapping for this method in 1.18, so its back to obfuscated method // 1.19 mapping is u and 1.18 is v - String isRunningMethod = Skript.isRunningMinecraft(1, 19) ? "u" : Skript.isRunningMinecraft(1, 18) ? "v" :"isRunning"; + String isRunningMethod = "isRunning"; + + if (Skript.isRunningMinecraft(1, 20)) { + isRunningMethod = "v"; + } else if (Skript.isRunningMinecraft(1, 19)) { + isRunningMethod = "u"; + } else if (Skript.isRunningMinecraft(1, 18)) { + isRunningMethod = "v"; + } IS_RUNNING = MC_SERVER.getClass().getMethod(isRunningMethod); } catch (NoSuchMethodException e) { throw new RuntimeException(e); @@ -1261,7 +1293,7 @@ public static boolean isAcceptRegistrations() { } public static void checkAcceptRegistrations() { - if (!isAcceptRegistrations()) + if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } @@ -1436,6 +1468,7 @@ public boolean check(final @Nullable ExpressionInfo i) { // ================ EVENTS ================ + private static final List> events = new ArrayList<>(50); private static final List> structures = new ArrayList<>(10); /** @@ -1468,10 +1501,10 @@ public static SkriptEventInfo registerEvent(String na String[] transformedPatterns = new String[patterns.length]; for (int i = 0; i < patterns.length; i++) - transformedPatterns[i] = "[on] " + SkriptEvent.fixPattern(patterns[i]) + SkriptEventInfo.EVENT_PRIORITY_SYNTAX; + transformedPatterns[i] = SkriptEvent.fixPattern(patterns[i]); SkriptEventInfo r = new SkriptEventInfo<>(name, transformedPatterns, c, originClassPath, events); - structures.add(r); + Skript.events.add(r); return r; } @@ -1489,14 +1522,8 @@ public static void registerStructure(Class c, EntryVali structures.add(structureInfo); } - /** - * Modifications made to the returned Collection will not be reflected in the events available for parsing. - */ public static Collection> getEvents() { - // Only used in documentation generation, so generating a new list each time is fine - return (Collection>) (Collection) structures.stream() - .filter(info -> info instanceof SkriptEventInfo) - .collect(Collectors.toList()); + return events; } public static List> getStructures() { diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index a96ed7bf3ca..a8cb74a869b 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -97,14 +97,15 @@ public class SkriptConfig { .setter(t -> { ReleaseChannel channel; switch (t) { - case "alpha": // Everything goes in alpha channel - channel = new ReleaseChannel((name) -> true, t); - break; + case "alpha": case "beta": - channel = new ReleaseChannel((name) -> !name.contains("alpha"), t); + Skript.warning("'alpha' and 'beta' are no longer valid release channels. Use 'prerelease' instead."); + case "prerelease": // All development builds are valid + channel = new ReleaseChannel((name) -> true, t); break; case "stable": - channel = new ReleaseChannel((name) -> !name.contains("alpha") && !name.contains("beta"), t); + // TODO a better option would be to check that it is not a pre-release through GH API + channel = new ReleaseChannel((name) -> !name.contains("pre"), t); break; case "none": channel = new ReleaseChannel((name) -> false, t); @@ -116,9 +117,6 @@ public class SkriptConfig { } SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) { - if (updater.getCurrentRelease().flavor.contains("spigot") && !t.equals("stable")) { - Skript.error("Only stable Skript versions are uploaded to Spigot resources."); - } updater.setReleaseChannel(channel); } }); @@ -246,8 +244,7 @@ public static String formatDate(final long timestamp) { }); public static final Option caseInsensitiveVariables = new Option<>("case-insensitive variables", true) - .setter(t -> Variables.caseInsensitiveVariables = t) - .optional(true); + .setter(t -> Variables.caseInsensitiveVariables = t); public static final Option colorResetCodes = new Option<>("color codes reset formatting", true) .setter(t -> { diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 28eaff9b23c..30954ba5cf9 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,17 +18,12 @@ */ package ch.njol.skript; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; - +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.util.Task; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -42,13 +37,16 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.timings.SkriptTimings; -import ch.njol.skript.util.Task; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; public final class SkriptEventHandler { @@ -112,66 +110,94 @@ private static List getTriggers(Class event) { * @param priority The priority of the Event. */ private static void check(Event event, EventPriority priority) { + // get all triggers for this event, return if none List triggers = getTriggers(event.getClass()); if (triggers.isEmpty()) return; - if (Skript.logVeryHigh()) { - boolean hasTrigger = false; - for (Trigger trigger : triggers) { - SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() == priority && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { - hasTrigger = true; - break; - } - } - if (!hasTrigger) - return; - - logEventStart(event); - } - - boolean isCancelled = event instanceof Cancellable && ((Cancellable) event).isCancelled() && !listenCancelled.contains(event.getClass()); - boolean isResultDeny = !(event instanceof PlayerInteractEvent && (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); + // Check if this event should be treated as cancelled + boolean isCancelled = isCancelled(event); - if (isCancelled && isResultDeny) { - if (Skript.logVeryHigh()) - Skript.info(" -x- was cancelled"); - return; - } + // This logs events even if there isn't a trigger that's going to run at that priority. + // However, there should only be a priority listener IF there's a trigger at that priority. + // So the time will be logged even if no triggers pass check(), which is still useful information. + logEventStart(event, priority); for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); + + // check if the trigger is at the right priority if (triggerEvent.getEventPriority() != priority) continue; - // these methods need to be run on whatever thread the trigger is - Runnable execute = () -> { - logTriggerStart(trigger); - Object timing = SkriptTimings.start(trigger.getDebugLabel()); - trigger.execute(event); - SkriptTimings.stop(timing); - logTriggerEnd(trigger); - }; - - if (trigger.getEvent().canExecuteAsynchronously()) { - // check should be performed on the main thread - if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) - continue; - execute.run(); - } else { // Ensure main thread - Task.callSync(() -> { - if (!triggerEvent.check(event)) - return null; - execute.run(); - return null; // we don't care about a return value - }); - } + // check if the cancel state of the event is correct + if (!triggerEvent.getListeningBehavior().matches(isCancelled)) + continue; + + // execute the trigger + execute(trigger, event); } logEventEnd(); } + /** + * Helper method to check if we should treat the provided Event as cancelled. + * + * @param event The event to check. + * @return Whether the event should be treated as cancelled. + */ + private static boolean isCancelled(Event event) { + return event instanceof Cancellable && + (((Cancellable) event).isCancelled() && isResultDeny(event)) && + // TODO: listenCancelled is deprecated and should be removed in 2.10 + !listenCancelled.contains(event.getClass()); + } + + /** + * Helper method for when the provided Event is a {@link PlayerInteractEvent}. + * These events are special in that they are called as cancelled when the player is left/right clicking on air. + * We don't want to treat those as cancelled, so we need to check if the {@link PlayerInteractEvent#useItemInHand()} result is DENY. + * That means the event was purposefully cancelled, and we should treat it as cancelled. + * + * @param event The event to check. + * @return Whether the event was a PlayerInteractEvent with air and the result was DENY. + */ + private static boolean isResultDeny(Event event) { + return !(event instanceof PlayerInteractEvent && + (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && + ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); + } + + /** + * Executes the provided Trigger with the provided Event as context. + * + * @param trigger The Trigger to execute. + * @param event The Event to execute the Trigger with. + */ + private static void execute(Trigger trigger, Event event) { + // these methods need to be run on whatever thread the trigger is + Runnable execute = () -> { + logTriggerStart(trigger); + Object timing = SkriptTimings.start(trigger.getDebugLabel()); + trigger.execute(event); + SkriptTimings.stop(timing); + logTriggerEnd(trigger); + }; + + if (trigger.getEvent().canExecuteAsynchronously()) { + if (trigger.getEvent().check(event)) + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (trigger.getEvent().check(event)) + execute.run(); + return null; // we don't care about a return value + }); + } + } + + private static long startEvent; /** @@ -180,11 +206,30 @@ private static void check(Event event, EventPriority priority) { * @param event The Event that started. */ public static void logEventStart(Event event) { + logEventStart(event, null); + } + + /** + * Logs that the provided Event has started with a priority. + * Requires {@link Skript#logVeryHigh()} to be true to log anything. + * @param event The Event that started. + * @param priority The priority of the Event. + */ + public static void logEventStart(Event event, @Nullable EventPriority priority) { startEvent = System.nanoTime(); if (!Skript.logVeryHigh()) return; Skript.info(""); - Skript.info("== " + event.getClass().getName() + " =="); + + String message = "== " + event.getClass().getName(); + + if (priority != null) + message += " with priority " + priority; + + if (event instanceof Cancellable && ((Cancellable) event).isCancelled()) + message += " (cancelled)"; + + Skript.info(message + " =="); } /** @@ -307,8 +352,10 @@ public static void unregisterBukkitEvents(Trigger trigger) { } /** - * Events which are listened even if they are cancelled. + * Events which are listened even if they are cancelled. This should no longer be used. + * @deprecated Users should specify the listening behavior in the event declaration. "on any %event%:", "on cancelled %event%:". */ + @Deprecated public static final Set> listenCancelled = new HashSet<>(); /** diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index 1c6e73184c4..c4fc7b4204a 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -21,10 +21,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Set; import java.util.List; import java.util.Map; +import ch.njol.skript.Skript; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -42,6 +44,9 @@ * Provides aliases on Bukkit/Spigot platform. */ public class AliasesProvider { + + // not supported on Spigot versions older than 1.18 + private static final boolean FASTER_SET_SUPPORTED = Skript.classExists("it.unimi.dsi.fastutil.objects.ObjectOpenHashSet"); /** * When an alias is not found, it will requested from this provider. @@ -173,7 +178,12 @@ public AliasesProvider(int expectedCount, @Nullable AliasesProvider parent) { this.aliases = new HashMap<>(expectedCount); this.variations = new HashMap<>(expectedCount / 20); this.aliasesMap = new AliasesMap(); - this.materials = new ObjectOpenHashSet<>(); + + if (FASTER_SET_SUPPORTED) { + this.materials = new ObjectOpenHashSet<>(); + } else { + this.materials = new HashSet<>(); + } this.gson = new Gson(); } diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index b6f90b30f42..32323c57e6f 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -31,6 +31,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemFlag; @@ -188,15 +189,23 @@ public ItemData(ItemStack stack) { this(stack, BlockCompat.INSTANCE.getBlockValues(stack)); this.itemForm = true; } - - public ItemData(BlockState block) { - this.type = ItemUtils.asItem(block.getType()); + + /** + * @deprecated Use {@link ItemData#ItemData(BlockData)} instead + */ + @Deprecated + public ItemData(BlockState blockState) { + this(blockState.getBlockData()); + } + + public ItemData(BlockData blockData) { + this.type = blockData.getMaterial(); this.stack = new ItemStack(type); - this.blockValues = BlockCompat.INSTANCE.getBlockValues(block); + this.blockValues = BlockCompat.INSTANCE.getBlockValues(blockData); } public ItemData(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 83d58f1ca4c..7341667fa91 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -18,37 +18,6 @@ */ package ch.njol.skript.aliases; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Random; -import java.util.RandomAccess; -import java.util.Set; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.ItemData.OldItemData; import ch.njol.skript.bukkitutil.BukkitUnsafe; import ch.njol.skript.bukkitutil.ItemUtils; @@ -68,14 +37,45 @@ import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.Fields.FieldContext; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.block.data.BlockData; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.RandomAccess; +import java.util.Set; @ContainerType(ItemStack.class) public class ItemType implements Unit, Iterable, Container, YggdrasilExtendedSerializable { - + static { // This handles updating ItemType and ItemData variable records Variables.yggdrasil.registerFieldHandler(new FieldHandler() { - + @Override public boolean missingField(Object o, Field field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -84,12 +84,12 @@ public boolean missingField(Object o, Field field) throws StreamCorruptedExcepti return true; // Just null, no need for updating that data return false; } - + @Override public boolean incompatibleField(Object o, Field f, FieldContext field) throws StreamCorruptedException { return false; } - + @Override public boolean excessiveField(Object o, FieldContext field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -101,7 +101,7 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt } }); } - + /** * DO NOT ADD ItemDatas to this list directly! *

@@ -109,31 +109,31 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt * can have its own ItemMeta. */ final ArrayList types = new ArrayList<>(2); - + /** * Whether this ItemType represents all types or not. */ private boolean all = false; - + /** * Amount determines how many items this type represents. Negative amounts * are treated as their absolute values when adding items to inventories * and otherwise used as "doesn't matter" flags. */ private int amount = -1; - + /** * ItemTypes to use instead of this one if adding to an inventory or setting a block. */ @Nullable private ItemType item = null, block = null; - + /** * Meta that applies for all ItemDatas there. */ @Nullable private ItemMeta globalMeta; - + void setItem(final @Nullable ItemType item) { if (equals(item)) { // can happen if someone defines a 'x' and 'x item/block' alias that have the same value, e.g. 'dirt' and 'dirt block' this.item = null; @@ -149,7 +149,7 @@ void setItem(final @Nullable ItemType item) { this.item = item; } } - + void setBlock(final @Nullable ItemType block) { if (equals(block)) { this.block = null; @@ -167,30 +167,36 @@ void setBlock(final @Nullable ItemType block) { } public ItemType() {} - + public ItemType(Material id) { add_(new ItemData(id)); } - + public ItemType(Material id, String tags) { add_(new ItemData(id, tags)); } - + public ItemType(ItemData d) { add_(d.clone()); } - + public ItemType(ItemStack i) { amount = i.getAmount(); add_(new ItemData(i)); } - - public ItemType(BlockState b) { -// amount = 1; - add_(new ItemData(b)); - // TODO metadata - spawners, skulls, etc. + + /** + * @deprecated Use {@link #ItemType(BlockData)} instead + */ + @Deprecated + public ItemType(BlockState blockState) { + this(blockState.getBlockData()); } - + + public ItemType(BlockData blockData) { + add_(new ItemData(blockData)); + } + /** * Copy constructor. * @param i Another ItemType. @@ -198,7 +204,7 @@ public ItemType(BlockState b) { private ItemType(ItemType i) { setTo(i); } - + public void setTo(ItemType i) { all = i.all; amount = i.amount; @@ -212,7 +218,7 @@ public void setTo(ItemType i) { } public ItemType(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** @@ -221,7 +227,7 @@ public ItemType(Block block) { public void modified() { item = block = null; } - + /** * Returns amount of the item in stack that this type represents. * @return amount. @@ -230,22 +236,22 @@ public void modified() { public int getAmount() { return Math.abs(amount); } - + /** * Only use this method if you know what you're doing. - * + * * @return The internal amount, i.e. same as {@link #getAmount()} * or additive inverse number of it. */ public int getInternalAmount() { return amount; } - + @Override public void setAmount(final double amount) { setAmount((int) amount); } - + public void setAmount(final int amount) { this.amount = amount; if (item != null) @@ -253,7 +259,7 @@ public void setAmount(final int amount) { if (block != null) block.amount = amount; } - + /** * Checks if this item type represents one of its items (OR) or all of * them (AND). If this has only one item, it doesn't matter. @@ -262,30 +268,38 @@ public void setAmount(final int amount) { public boolean isAll() { return all; } - + public void setAll(final boolean all) { this.all = all; } - + public boolean isOfType(@Nullable ItemStack item) { if (item == null) return isOfType(Material.AIR, null); return isOfType(new ItemData(item)); } - - public boolean isOfType(@Nullable BlockState block) { - if (block == null) + + /** + * @deprecated Use {@link #isOfType(BlockData)} instead + */ + @Deprecated + public boolean isOfType(@Nullable BlockState blockState) { + return blockState != null && isOfType(blockState.getBlockData()); + } + + public boolean isOfType(@Nullable BlockData blockData) { + if (blockData == null) return isOfType(Material.AIR, null); - - return isOfType(new ItemData(block)); + + return isOfType(new ItemData(blockData)); } - + public boolean isOfType(@Nullable Block block) { if (block == null) return isOfType(Material.AIR, null); - return isOfType(block.getState()); + return isOfType(block.getBlockData()); } - + public boolean isOfType(ItemData type) { for (final ItemData myType : types) { if (myType.equals(type)) { @@ -294,16 +308,16 @@ public boolean isOfType(ItemData type) { } return false; } - + public boolean isOfType(Material id, @Nullable String tags) { return isOfType(new ItemData(id, tags)); } - + public boolean isOfType(Material id) { // TODO avoid object creation return isOfType(new ItemData(id, null)); } - + /** * Checks if this type represents all the items represented by given * item type. This type may of course also represent other items. @@ -313,17 +327,17 @@ public boolean isOfType(Material id) { public boolean isSupertypeOf(ItemType other) { return types.containsAll(other.types); } - + public ItemType getItem() { final ItemType item = this.item; return item == null ? this : item; } - + public ItemType getBlock() { final ItemType block = this.block; return block == null ? this : block; } - + /** * @return Whether this ItemType has at least one ItemData that represents an item */ @@ -334,7 +348,7 @@ public boolean hasItem() { } return false; } - + /** * @return Whether this ItemType has at least one ItemData that represents a block */ @@ -353,10 +367,10 @@ public boolean hasBlock() { public boolean hasType() { return !types.isEmpty(); } - + /** * Sets the given block to this ItemType - * + * * @param block The block to set * @param applyPhysics Whether to run a physics check just after setting the block * @return Whether the block was successfully set @@ -382,7 +396,7 @@ public boolean setBlock(Block block, boolean applyPhysics) { } return false; } - + /** * Send a block change to a player *

This will send a fake block change to the player, and will not change the block on the server.

@@ -399,11 +413,11 @@ public void sendBlockChange(Player player, Location location) { BlockUtils.sendBlockChange(player, location, blockType, d.getBlockValues()); } } - + /** * Intersects all ItemDatas with all ItemDatas of the given ItemType, returning an ItemType with at most n*m ItemDatas, where n = #ItemDatas of this ItemType, and m = * #ItemDatas of the argument. - * + * * @see ItemData#intersection(ItemData) * @param other * @return A new item type which is the intersection of the two item types or null if the intersection is empty. @@ -421,7 +435,7 @@ public ItemType intersection(ItemType other) { return null; return r; } - + /** * @param type Some ItemData. Only a copy of it will be stored. */ @@ -430,7 +444,7 @@ public void add(@Nullable ItemData type) { add_(type.clone()); } } - + /** * @param type A cloned or newly created ItemData */ @@ -441,36 +455,36 @@ private void add_(@Nullable ItemData type) { modified(); } } - + public void addAll(Collection types) { this.types.addAll(types); modified(); } - + public void remove(ItemData type) { if (types.remove(type)) { //numItems -= type.numItems(); modified(); } } - + void remove(int index) { types.remove(index); //numItems -= type.numItems(); modified(); } - + @Override public Iterator containerIterator() { return new Iterator() { @SuppressWarnings("null") Iterator iter = types.iterator(); - + @Override public boolean hasNext() { return iter.hasNext(); } - + @Override public ItemStack next() { if (!hasNext()) @@ -479,17 +493,17 @@ public ItemStack next() { is.setAmount(getAmount()); return is; } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + /** * Gets all ItemStacks this ItemType represents. Only use this if you know what you're doing, as it returns only one element if this is not an 'every' alias. - * + * * @return An Iterable whose iterator will always return the same item(s) */ public Iterable getAll() { @@ -506,7 +520,7 @@ public Iterator iterator() { } }; } - + @Nullable public ItemStack removeAll(@Nullable ItemStack item) { boolean wasAll = all; @@ -520,10 +534,10 @@ public ItemStack removeAll(@Nullable ItemStack item) { amount = oldAmount; } } - + /** * Removes this type from the item stack if appropriate - * + * * @param item * @return The passed ItemStack or null if the resulting amount is <= 0 */ @@ -541,10 +555,10 @@ public ItemStack removeFrom(@Nullable ItemStack item) { item.setAmount(a); return item; } - + /** * Adds this ItemType to the given item stack - * + * * @param item * @return The passed ItemStack or a new one if the passed is null or air */ @@ -556,14 +570,14 @@ public ItemStack addTo(final @Nullable ItemStack item) { item.setAmount(Math.min(item.getAmount() + getAmount(), item.getMaxStackSize())); return item; } - + @Override public ItemType clone() { return new ItemType(this); } - + private final static Random random = new Random(); - + /** * @return One random ItemStack that this ItemType represents. If you have a List or an Inventory, use {@link #addTo(Inventory)} or {@link #addTo(List)} respectively. * @see #addTo(Inventory) @@ -581,13 +595,13 @@ public ItemStack getRandom() { is.setAmount(getAmount()); return is; } - + /** * Test whether this ItemType can be put into the given inventory completely. *

* REMIND If this ItemType represents multiple items with OR, this function will immediately return false.
* CondCanHold currently blocks aliases without 'every'/'all' as temporary solution. - * + * * @param invi * @return Whether this item type can be added to the given inventory */ @@ -598,7 +612,7 @@ public boolean hasSpace(final Inventory invi) { } return addTo(getStorageContents(invi)); } - + public static ItemStack[] getCopiedContents(Inventory invi) { final ItemStack[] buf = invi.getContents(); for (int i = 0; i < buf.length; i++) @@ -606,7 +620,7 @@ public static ItemStack[] getCopiedContents(Inventory invi) { buf[i] = buf[i].clone(); return buf; } - + /** * Gets copy of storage contents, i.e. ignores armor and off hand. This is due to Spigot 1.9 * added armor slots, and off hand to default inventory index. @@ -623,7 +637,7 @@ public static ItemStack[] getStorageContents(final Inventory invi) { return tBuf; } else return getCopiedContents(invi); } - + /** * @return List of ItemDatas. The returned list is not modifiable, use {@link #add(ItemData)} and {@link #remove(ItemData)} if you need to change the list, or use the * {@link #iterator()}. @@ -632,28 +646,28 @@ public static ItemStack[] getStorageContents(final Inventory invi) { public List getTypes() { return Collections.unmodifiableList(types); } - + public int numTypes() { return types.size(); } - + /** * @return How many different items this item type represents */ public int numItems() { return types.size(); } - + @Override public Iterator iterator() { return new Iterator() { private int next = 0; - + @Override public boolean hasNext() { return next < types.size(); } - + @SuppressWarnings("null") @Override public ItemData next() { @@ -661,7 +675,7 @@ public ItemData next() { throw new NoSuchElementException(); return types.get(next++); } - + @Override public void remove() { if (next <= 0) @@ -670,7 +684,7 @@ public void remove() { } }; } - + public boolean isContainedIn(Iterable items) { int needed = getAmount(); int found = 0; @@ -706,7 +720,7 @@ public boolean isContainedIn(ItemStack[] items) { return false; return all; } - + public boolean removeAll(Inventory invi) { final boolean wasAll = all; final int oldAmount = amount; @@ -719,58 +733,86 @@ public boolean removeAll(Inventory invi) { amount = oldAmount; } } - + /** * Removes this type from the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be removed from the inventory */ public boolean removeFrom(Inventory invi) { ItemStack[] buf = getCopiedContents(invi); - + final boolean ok = removeFrom(Arrays.asList(buf)); - + invi.setContents(buf); return ok; } - + @SafeVarargs public final boolean removeAll(List... lists) { + return removeAll(true, lists); + } + + + @SafeVarargs + public final boolean removeAll(boolean replaceWithNull, List...lists) { final boolean wasAll = all; final int oldAmount = amount; all = true; amount = -1; try { - return removeFrom(lists); + return removeFrom(replaceWithNull, lists); } finally { all = wasAll; amount = oldAmount; } } - + /** - * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. + * Removes this ItemType from given lists of ItemStacks. + * If an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method. * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) */ @SafeVarargs public final boolean removeFrom(final List... lists) { + return removeFrom(true, lists); + } + + /** + * Removes this ItemType from given lists of ItemStacks. + * If replaceWithNull is true, then if an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param replaceWithNull Whether to replace removed ItemStacks with null, or to remove them completely + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method if replaceWithNull is true. + * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) + */ + @SafeVarargs + public final boolean removeFrom(boolean replaceWithNull, List... lists) { int removed = 0; boolean ok = true; - - for (final ItemData d : types) { + + for (ItemData d : types) { if (all) removed = 0; - for (final List list : lists) { + for (List list : lists) { if (list == null) continue; assert list instanceof RandomAccess; - for (int i = 0; i < list.size(); i++) { - final ItemStack is = list.get(i); + + Iterator listIterator = list.iterator(); + int index = -1; // only reliable if replaceWithNull is true. Will be -1 if replaceWithNull is false. + while (listIterator.hasNext()) { + ItemStack is = listIterator.next(); + // index is only reliable if replaceWithNull is true + if (replaceWithNull) + index++; /* * Do NOT use equals()! It doesn't exactly match items * for historical reasons. This will change in future. - * + * * In Skript 2.3, equals() was used for getting closest * possible aliases for items. It was horribly hacky, and * is not done anymore. Still, some uses of equals() expect @@ -784,15 +826,22 @@ public final boolean removeFrom(final List... lists) { boolean plain = d.isPlain() != other.isPlain(); if (d.matchPlain(other) || other.matchAlias(d).isAtLeast(plain ? MatchQuality.EXACT : (d.isAlias() && !other.isAlias() ? MatchQuality.SAME_MATERIAL : MatchQuality.SAME_ITEM))) { if (all && amount == -1) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } removed = 1; continue; } - assert is != null; - final int toRemove = Math.min(is.getAmount(), getAmount() - removed); + int toRemove = Math.min(is.getAmount(), getAmount() - removed); removed += toRemove; if (toRemove == is.getAmount()) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } } else { is.setAmount(is.getAmount() - toRemove); } @@ -807,15 +856,15 @@ public final boolean removeFrom(final List... lists) { if (all) ok &= removed == getAmount(); } - + if (!all) return false; return ok; } - + /** * Adds this ItemType to the given list, without filling existing stacks. - * + * * @param list */ public void addTo(final List list) { @@ -826,17 +875,17 @@ public void addTo(final List list) { for (final ItemStack is : getItem().getAll()) list.add(is); } - + /** * Tries to add this ItemType to the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be added to the inventory */ public boolean addTo(final Inventory invi) { // important: don't use inventory.add() - it ignores max stack sizes ItemStack[] buf = invi.getContents(); - + ItemStack[] tBuf = buf.clone(); if (invi instanceof PlayerInventory) { buf = new ItemStack[36]; @@ -844,21 +893,21 @@ public boolean addTo(final Inventory invi) { buf[i] = tBuf[i]; } } - + final boolean b = addTo(buf); - + if (invi instanceof PlayerInventory) { buf = Arrays.copyOf(buf, tBuf.length); for (int i = tBuf.length - 5; i < tBuf.length; ++i) { buf[i] = tBuf[i]; } } - + assert buf != null; invi.setContents(buf); return b; } - + private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { if (is == null || is.getType() == Material.AIR) return true; @@ -884,7 +933,7 @@ private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { } return false; } - + public boolean addTo(final ItemStack[] buf) { if (!isAll()) { return addTo(getItem().getRandom(), buf); @@ -895,12 +944,12 @@ public boolean addTo(final ItemStack[] buf) { } return ok; } - + /** * Tests whether a given set of ItemTypes is a subset of another set of ItemTypes. *

* This method works differently that normal set operations, as is e.g. returns true if set == {everything}. - * + * * @param set * @param sub * @return Whether all item types in sub have at least one {@link #isSupertypeOf(ItemType) super type} in set @@ -916,7 +965,7 @@ public static boolean isSubset(final ItemType[] set, final ItemType[] sub) { } return true; } - + @Override public boolean equals(final @Nullable Object obj) { if (this == obj) @@ -973,7 +1022,7 @@ public boolean isSimilar(ItemType other) { } return false; } - + @Override public int hashCode() { final int prime = 31; @@ -983,21 +1032,21 @@ public int hashCode() { result = prime * result + types.hashCode(); return result; } - + @Override public String toString() { return toString(false, 0, null); } - + @Override public String toString(final int flags) { return toString(false, flags, null); } - + public String toString(final int flags, final @Nullable Adjective a) { return toString(false, flags, a); } - + private String toString(final boolean debug, final int flags, final @Nullable Adjective a) { final StringBuilder b = new StringBuilder(); // if (types.size() == 1 && !types.get(0).hasDataRange()) { @@ -1058,33 +1107,33 @@ private String toString(final boolean debug, final int flags, final @Nullable Ad // } return "" + b.toString(); } - + public static String toString(final ItemStack i) { return new ItemType(i).toString(); } - + public static String toString(final ItemStack i, final int flags) { return new ItemType(i).toString(flags); } - + public static String toString(Block b, int flags) { return new ItemType(b).toString(flags); } - + public String getDebugMessage() { return toString(true, 0, null); } - + @Override public Fields serialize() throws NotSerializableException { final Fields f = new Fields(this); return f; } - + @Override public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { fields.setFields(this); - + // Legacy data (before aliases rework) update if (!types.isEmpty()) { @SuppressWarnings("rawtypes") @@ -1103,7 +1152,7 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No } } } - + /** * Gets raw item names ("minecraft:some_item"). If they are not available, * empty list will be returned. @@ -1117,10 +1166,10 @@ public List getRawNames() { if (id != null) rawNames.add(id); } - + return rawNames; } - + /** * Gets all enchantments of this item. * @return Enchantments. @@ -1137,7 +1186,7 @@ public Map getEnchantments() { return null; return enchants; } - + /** * Adds enchantments to this item type. * @param enchantments Enchantments. @@ -1152,7 +1201,7 @@ public void addEnchantments(Map enchantments) { globalMeta.addEnchant(entry.getKey(), entry.getValue(), true); } } - + /** * Gets all enchantments of this item. * @return the enchantments of this item type. @@ -1160,7 +1209,7 @@ public void addEnchantments(Map enchantments) { @Nullable public EnchantmentType[] getEnchantmentTypes() { Set> enchants = getItemMeta().getEnchants().entrySet(); - + return enchants.stream() .map(enchant -> new EnchantmentType(enchant.getKey(), enchant.getValue())) .toArray(EnchantmentType[]::new); @@ -1182,14 +1231,14 @@ public EnchantmentType getEnchantmentType(Enchantment enchantment) { .findFirst() .orElse(null); } - + /** * Checks whether this item type has enchantments. */ public boolean hasEnchantments() { return getItemMeta().hasEnchants(); } - + /** * Checks whether this item type has the given enchantments. * @param enchantments the enchantments to be checked. @@ -1198,14 +1247,14 @@ public boolean hasEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { if (!meta.hasEnchant(enchantment)) return false; } return true; } - + /** * Checks whether this item type contains at most one of the given enchantments. * @param enchantments The enchantments to be checked. @@ -1214,7 +1263,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { assert enchantment != null; if (meta.hasEnchant(enchantment)) @@ -1222,7 +1271,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { } return false; } - + /** * Checks whether this item type contains the given enchantments. * Also checks the enchantment level. @@ -1242,14 +1291,14 @@ public boolean hasEnchantments(EnchantmentType... enchantments) { } return true; } - + /** * Adds the given enchantments to the item type. * @param enchantments The enchantments to be added. */ public void addEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1257,14 +1306,14 @@ public void addEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Removes the given enchantments from this item type. * @param enchantments The enchantments to be removed. */ public void removeEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1272,14 +1321,14 @@ public void removeEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Clears all enchantments from this item type except the ones that are * defined for individual item datas only. */ public void clearEnchantments() { ItemMeta meta = getItemMeta(); - + Set enchants = meta.getEnchants().keySet(); for (Enchantment ench : enchants) { assert ench != null; @@ -1287,7 +1336,7 @@ public void clearEnchantments() { } setItemMeta(meta); } - + /** * Gets item meta that applies to all items represented by this type. * @return Item meta. @@ -1303,13 +1352,13 @@ public ItemMeta getItemMeta() { */ public void setItemMeta(ItemMeta meta) { globalMeta = meta; - + // Apply new meta to all datas for (ItemData data : types) { data.setItemMeta(meta); } } - + /** * Clears item meta from this type. Metas which individual item dates may * have will not be touched. diff --git a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java index 9af9012eb7f..20b5120a0f1 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java @@ -18,13 +18,21 @@ */ package ch.njol.skript.bukkitutil; +import ch.njol.skript.Skript; import ch.njol.util.Math2; import org.bukkit.attribute.Attributable; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; import org.bukkit.entity.Damageable; 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 { @@ -107,9 +115,28 @@ public static double getFinalDamage(EntityDamageEvent e) { public static void setDamage(EntityDamageEvent e, double damage) { e.setDamage(damage * 2); } - + + @Nullable + private static final Constructor OLD_DAMAGE_EVENT_CONSTRUCTOR; + + static { + Constructor constructor = null; + try { + constructor = EntityDamageEvent.class.getConstructor(Damageable.class, DamageCause.class, double.class); + } catch (NoSuchMethodException ignored) { } + OLD_DAMAGE_EVENT_CONSTRUCTOR = constructor; + } + public static void setDamageCause(Damageable e, DamageCause cause) { - e.setLastDamageCause(new EntityDamageEvent(e, cause, 0)); + if (OLD_DAMAGE_EVENT_CONSTRUCTOR != null) { + try { + e.setLastDamageCause(OLD_DAMAGE_EVENT_CONSTRUCTOR.newInstance(e, cause, 0)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { + Skript.exception("Failed to set last damage cause"); + } + } else { + e.setLastDamageCause(new EntityDamageEvent(e, cause, DamageSource.builder(DamageType.GENERIC).build(), 0)); + } } - + } diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 1fd542ed4db..c6d5f25870e 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -19,6 +19,7 @@ package ch.njol.skript.bukkitutil; import org.bukkit.Material; +import org.bukkit.TreeType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; @@ -26,6 +27,8 @@ import ch.njol.skript.Skript; +import java.util.HashMap; + /** * Miscellaneous static utility methods related to items. */ @@ -71,13 +74,15 @@ public static Material asBlock(Material type) { return null; } } - + /** * Gets an item material corresponding to given block material, which might * be the given material. * @param type Material. * @return Item version of material or null. + * @deprecated This just returns itself and has no use */ + @Deprecated public static Material asItem(Material type) { // Assume (naively) that all types are valid items return type; @@ -105,5 +110,62 @@ public static boolean isAir(Material type) { return type.isAir(); return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; } + + // TreeType -> Sapling (Material) conversion for EvtGrow + private static final HashMap TREE_TO_SAPLING_MAP = new HashMap<>(); + static { + // Populate TREE_TO_SAPLING_MAP + // oak + TREE_TO_SAPLING_MAP.put(TreeType.TREE, Material.OAK_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.BIG_TREE, Material.OAK_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.SWAMP, Material.OAK_SAPLING); + // spruce + TREE_TO_SAPLING_MAP.put(TreeType.REDWOOD, Material.SPRUCE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_REDWOOD, Material.SPRUCE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.MEGA_REDWOOD, Material.SPRUCE_SAPLING); + // birch + TREE_TO_SAPLING_MAP.put(TreeType.BIRCH, Material.BIRCH_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_BIRCH, Material.BIRCH_SAPLING); + // jungle + TREE_TO_SAPLING_MAP.put(TreeType.JUNGLE, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.SMALL_JUNGLE, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.JUNGLE_BUSH, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.COCOA_TREE, Material.JUNGLE_SAPLING); + // acacia + TREE_TO_SAPLING_MAP.put(TreeType.ACACIA, Material.ACACIA_SAPLING); + // dark oak + TREE_TO_SAPLING_MAP.put(TreeType.DARK_OAK, Material.DARK_OAK_SAPLING); + + // mushrooms + TREE_TO_SAPLING_MAP.put(TreeType.BROWN_MUSHROOM, Material.BROWN_MUSHROOM); + TREE_TO_SAPLING_MAP.put(TreeType.RED_MUSHROOM, Material.RED_MUSHROOM); + + // chorus + TREE_TO_SAPLING_MAP.put(TreeType.CHORUS_PLANT, Material.CHORUS_FLOWER); + + // nether + if (Skript.isRunningMinecraft(1, 16)) { + TREE_TO_SAPLING_MAP.put(TreeType.WARPED_FUNGUS, Material.WARPED_FUNGUS); + TREE_TO_SAPLING_MAP.put(TreeType.CRIMSON_FUNGUS, Material.CRIMSON_FUNGUS); + } + + // azalea + if (Skript.isRunningMinecraft(1, 17)) + TREE_TO_SAPLING_MAP.put(TreeType.AZALEA, Material.AZALEA); + + // mangrove + if (Skript.isRunningMinecraft(1, 19)) { + TREE_TO_SAPLING_MAP.put(TreeType.MANGROVE, Material.MANGROVE_PROPAGULE); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_MANGROVE, Material.MANGROVE_PROPAGULE); + } + + // cherry + if (Skript.isRunningMinecraft(1, 19, 4)) + TREE_TO_SAPLING_MAP.put(TreeType.CHERRY, Material.CHERRY_SAPLING); + } + + public static Material getTreeSapling(TreeType treeType) { + return TREE_TO_SAPLING_MAP.get(treeType); + } } 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 5ffb1063284..eef0866a01b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java @@ -23,11 +23,11 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.FallingBlock; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemFlags; /** @@ -42,13 +42,15 @@ public interface BlockCompat { BlockCompat INSTANCE = new NewBlockCompat(); static final BlockSetter SETTER = INSTANCE.getSetter(); - + /** * Gets block values from a block state. They can be compared to other * values if needed, but cannot be used to retrieve any other data. * @param block Block state to retrieve value from. * @return Block values. + * @deprecated Use {@link #getBlockValues(BlockData)} instead */ + @Deprecated @Nullable BlockValues getBlockValues(BlockState block); @@ -60,8 +62,11 @@ public interface BlockCompat { */ @Nullable default BlockValues getBlockValues(Block block) { - return getBlockValues(block.getState()); + return getBlockValues(block.getBlockData()); } + + @Nullable + BlockValues getBlockValues(BlockData blockData); /** * Gets block values from a item stack. They can be compared to other values @@ -71,17 +76,19 @@ default BlockValues getBlockValues(Block block) { */ @Nullable BlockValues getBlockValues(ItemStack stack); - + /** * Creates a block state from a falling block. * @param entity Falling block entity * @return Block state. + * @deprecated This shouldn't be used */ + @Deprecated BlockState fallingBlockToState(FallingBlock entity); - + @Nullable default BlockValues getBlockValues(FallingBlock entity) { - return getBlockValues(fallingBlockToState(entity)); + return getBlockValues(entity.getBlockData()); } /** 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 0610022d1c9..2a0ecd36da8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -325,16 +325,23 @@ public void sendBlockChange(Player player, Location location, Material type, @Nu } private NewBlockSetter setter = new NewBlockSetter(); - + + /** + * @deprecated Use {@link #getBlockValues(BlockData)} instead + */ + @Deprecated @Nullable @Override - public BlockValues getBlockValues(BlockState block) { - // If block doesn't have useful data, data field of type is MaterialData - if (block.getType().isBlock()) - return new NewBlockValues(block.getType(), block.getBlockData(), false); - return null; + public BlockValues getBlockValues(BlockState blockState) { + return getBlockValues(blockState.getBlockData()); } - + + @Nullable + @Override + public BlockValues getBlockValues(BlockData blockData) { + return new NewBlockValues(blockData.getMaterial(), blockData, false); + } + @Override @Nullable public BlockValues getBlockValues(ItemStack stack) { @@ -345,15 +352,16 @@ public BlockValues getBlockValues(ItemStack stack) { } return null; } - + @Override public BlockSetter getSetter() { return setter; } + @Deprecated @Override public BlockState fallingBlockToState(FallingBlock entity) { - BlockState state = entity.getWorld().getBlockAt(0, 0, 0).getState(); + BlockState state = entity.getLocation().getBlock().getState(); state.setBlockData(entity.getBlockData()); return state; } diff --git a/src/main/java/ch/njol/skript/classes/Arithmetic.java b/src/main/java/ch/njol/skript/classes/Arithmetic.java index 85461a67044..2365c3a64da 100644 --- a/src/main/java/ch/njol/skript/classes/Arithmetic.java +++ b/src/main/java/ch/njol/skript/classes/Arithmetic.java @@ -24,6 +24,7 @@ * @param the type of the absolute value * @param the type of the relative value */ +@Deprecated public interface Arithmetic { public R difference(A first, A second); diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 3dce029edb5..30211480a05 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -18,26 +18,27 @@ */ package ch.njol.skript.classes; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - import ch.njol.skript.SkriptAPIException; -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 ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.Debuggable; import ch.njol.skript.lang.DefaultExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Noun; +import ch.njol.util.coll.iterator.ArrayIterator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.skriptlang.skript.lang.arithmetic.Operator; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; /** * @author Peter Güttinger @@ -223,11 +224,19 @@ public ClassInfo changer(final Changer changer) { this.changer = changer; return this; } - + + @Deprecated + @SuppressWarnings("unchecked") public ClassInfo math(final Class relativeType, final Arithmetic math) { assert this.math == null; this.math = math; mathRelativeType = relativeType; + Arithmetics.registerOperation(Operator.ADDITION, c, relativeType, (left, right) -> (T) math.add(left, right)); + Arithmetics.registerOperation(Operator.SUBTRACTION, c, relativeType, (left, right) -> (T) math.subtract(left, right)); + Arithmetics.registerOperation(Operator.MULTIPLICATION, c, relativeType, (left, right) -> (T) math.multiply(left, right)); + Arithmetics.registerOperation(Operator.DIVISION, c, relativeType, (left, right) -> (T) math.divide(left, right)); + Arithmetics.registerOperation(Operator.EXPONENTIATION, c, relativeType, (left, right) -> (T) math.power(left, right)); + Arithmetics.registerDifference(c, relativeType, math::difference); return this; } @@ -388,16 +397,19 @@ public Class getSerializeAs() { } @Nullable + @Deprecated public Arithmetic getMath() { return math; } @Nullable + @Deprecated public Arithmetic getRelativeMath() { return (Arithmetic) math; } @Nullable + @Deprecated public Class getMathRelativeType() { return mathRelativeType; } @@ -515,7 +527,7 @@ public String toString(final int flags) { @Override @NonNull - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(final @Nullable Event event, final boolean debug) { if (debug) return codeName + " (" + c.getCanonicalName() + ")"; return getName().getSingular(); diff --git a/src/main/java/ch/njol/skript/classes/Converter.java b/src/main/java/ch/njol/skript/classes/Converter.java index d2162d6eccf..0cb0da707b9 100644 --- a/src/main/java/ch/njol/skript/classes/Converter.java +++ b/src/main/java/ch/njol/skript/classes/Converter.java @@ -18,16 +18,15 @@ */ package ch.njol.skript.classes; -import java.util.Arrays; -import java.util.stream.Collectors; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Debuggable; import ch.njol.skript.registrations.Classes; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.converter.Converters; +import java.util.Arrays; +import java.util.stream.Collectors; + /** * Converts data from type to another. * @@ -98,7 +97,7 @@ public ConverterInfo(ConverterInfo first, ConverterInfo second, Conver } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (debug) { String str = Arrays.stream(chain).map(c -> Classes.getExactClassName(c)).collect(Collectors.joining(" -> ")); assert str != null; diff --git a/src/main/java/ch/njol/skript/classes/NumberArithmetic.java b/src/main/java/ch/njol/skript/classes/NumberArithmetic.java index 377e3feccce..71c035ebd7d 100644 --- a/src/main/java/ch/njol/skript/classes/NumberArithmetic.java +++ b/src/main/java/ch/njol/skript/classes/NumberArithmetic.java @@ -21,6 +21,7 @@ /** * @author Peter Güttinger */ +@Deprecated public class NumberArithmetic implements Arithmetic { @Override diff --git a/src/main/java/ch/njol/skript/classes/VectorArithmethic.java b/src/main/java/ch/njol/skript/classes/VectorArithmethic.java index d907075357f..5bdac60af9a 100644 --- a/src/main/java/ch/njol/skript/classes/VectorArithmethic.java +++ b/src/main/java/ch/njol/skript/classes/VectorArithmethic.java @@ -23,6 +23,7 @@ /** * @author bi0qaw */ +@Deprecated public class VectorArithmethic implements Arithmetic { @Override diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 6e17db006da..25000a2390d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -19,6 +19,7 @@ package ch.njol.skript.classes.data; import java.io.StreamCorruptedException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -59,6 +60,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryCloseEvent; @@ -66,6 +68,7 @@ import org.bukkit.event.player.PlayerQuitEvent.QuitReason; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.BlockInventoryHolder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -153,7 +156,7 @@ public Entity parse(final String s, final ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -536,7 +539,7 @@ protected boolean canBeInstantiated() { @Nullable public World parse(final String s, final ParseContext context) { // REMIND allow shortcuts '[over]world', 'nether' and '[the_]end' (server.properties: 'level-name=world') // inconsistent with 'world is "..."' - if (context == ParseContext.COMMAND || context == ParseContext.CONFIG) + if (context == ParseContext.COMMAND || context == ParseContext.PARSE || context == ParseContext.CONFIG) return Bukkit.getWorld(s); final Matcher m = parsePattern.matcher(s); if (m.matches()) @@ -659,32 +662,46 @@ public String toVariableNameString(final Inventory i) { Classes.registerClass(new ClassInfo<>(Player.class, "player") .user("players?") .name("Player") - .description("A player. Depending on whether a player is online or offline several actions can be performed with them, " + - "though you won't get any errors when using effects that only work if the player is online (e.g. changing their inventory) on an offline player.", + .description( + "A player. Depending on whether a player is online or offline several actions can be performed with them, " + + "though you won't get any errors when using effects that only work if the player is online (e.g. changing their inventory) on an offline player.", "You have two possibilities to use players as command arguments: <player> and <offline player>. " + - "The first requires that the player is online and also accepts only part of the name, " + - "while the latter doesn't require that the player is online, but the player's name has to be entered exactly.") - .usage("") - .examples("") - .since("1.0") + "The first requires that the player is online and also accepts only part of the name, " + + "while the latter doesn't require that the player is online, but the player's name has to be entered exactly." + ).usage( + "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + + "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." + ).examples( + "set {_p} to \"Notch\" parsed as a player # returns unless Notch is actually online or starts with Notch like Notchan", + "set {_p} to \"N\" parsed as a player # returns Notch if Notch is online because their name starts with 'N' (case insensitive) however, it would return nothing if no player whose name starts with 'N' is online." + ).since("1.0") .defaultExpression(new EventValueExpression<>(Player.class)) .after("string", "world") .parser(new Parser() { @Override @Nullable - public Player parse(String s, ParseContext context) { - if (context == ParseContext.COMMAND) { - if (s.isEmpty()) + public Player parse(String string, ParseContext context) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { + if (string.isEmpty()) return null; - if (UUID_PATTERN.matcher(s).matches()) - return Bukkit.getPlayer(UUID.fromString(s)); - List ps = Bukkit.matchPlayer(s); - if (ps.size() == 1) - return ps.get(0); - if (ps.size() == 0) - Skript.error(String.format(Language.get("commands.no player starts with"), s)); + if (UUID_PATTERN.matcher(string).matches()) + return Bukkit.getPlayer(UUID.fromString(string)); + String name = string.toLowerCase(Locale.ENGLISH); + int nameLength = name.length(); // caching + List players = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase(Locale.ENGLISH).startsWith(name)) { + if (player.getName().length() == nameLength) // a little better in performance than String#equals() + return player; + players.add(player); + } + } + if (players.size() == 1) + return players.get(0); + if (players.size() == 0) + Skript.error(String.format(Language.get("commands.no player starts with"), string)); else - Skript.error(String.format(Language.get("commands.multiple players start with"), s)); + Skript.error(String.format(Language.get("commands.multiple players start with"), string)); return null; } assert false; @@ -693,7 +710,7 @@ public Player parse(String s, ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -720,20 +737,22 @@ public String getDebugMessage(final Player p) { Classes.registerClass(new ClassInfo<>(OfflinePlayer.class, "offlineplayer") .user("offline ?players?") .name("Offline Player") - .description("A player that is possibly offline. See player for more information. " + + .description( + "A player that is possibly offline. See player for more information. " + "Please note that while all effects and conditions that require a player can be used with an " + - "offline player as well, they will not work if the player is not actually online.") - .usage("") - .examples("") - .since("") + "offline player as well, they will not work if the player is not actually online." + ).usage( + "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + + "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." + ).examples("set {_p} to \"Notch\" parsed as an offlineplayer # returns Notch even if they're offline") + .since("2.0 beta 8") .defaultExpression(new EventValueExpression<>(OfflinePlayer.class)) .after("string", "world") .parser(new Parser() { @Override @Nullable - @SuppressWarnings("deprecation") public OfflinePlayer parse(final String s, final ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (UUID_PATTERN.matcher(s).matches()) return Bukkit.getOfflinePlayer(UUID.fromString(s)); else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @@ -746,7 +765,7 @@ else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @Override public boolean canParse(ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -874,6 +893,10 @@ public String toString(InventoryHolder holder, int flags) { return Classes.toString(((BlockState) holder).getBlock()); } else if (holder instanceof DoubleChest) { return Classes.toString(holder.getInventory().getLocation().getBlock()); + } else if (holder instanceof BlockInventoryHolder) { + return Classes.toString(((BlockInventoryHolder) holder).getBlock()); + } else if (Classes.getSuperClassInfo(holder.getClass()).getC() == InventoryHolder.class) { + return holder.getClass().getSimpleName(); // an inventory holder and only that } else { return Classes.toString(holder); } @@ -1411,7 +1434,7 @@ public String toVariableNameString(FireworkEffect effect) { .user("(panda )?genes?") .name("Gene") .description("Represents a Panda's main or hidden gene. " + - "See genetics for more info.") + "See genetics for more info.") .since("2.4") .requiredPlugins("Minecraft 1.14 or newer")); } @@ -1487,7 +1510,7 @@ public String toVariableNameString(EnchantmentOffer eo) { .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.") .since("2.5")); Classes.registerClass(new EnumClassInfo<>(Environment.class, "environment", "environments") @@ -1510,7 +1533,7 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("Quit Reason") .description("Represents a quit reason from a player quit server event.") .requiredPlugins("Paper 1.16.5+") - .since("INSERT VERSION")); + .since("2.8.0")); if (Skript.classExists("org.bukkit.event.inventory.InventoryCloseEvent$Reason")) Classes.registerClass(new EnumClassInfo<>(InventoryCloseEvent.Reason.class, "inventoryclosereason", "inventory close reasons") @@ -1518,7 +1541,13 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("Inventory Close Reasons") .description("The inventory close reason in an inventory close event.") .requiredPlugins("Paper") - .since("INSERT VERSION")); + .since("2.8.0")); + + Classes.registerClass(new EnumClassInfo<>(TransformReason.class, "transformreason", "transform reasons") + .user("(entity)? ?transform ?(reason|cause)s?") + .name("Transform Reason") + .description("Represents a transform reason of an entity transform event.") + .since("2.8.0")); } } 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 ace15fc3a5f..148e0609171 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -37,12 +37,14 @@ import ch.njol.skript.util.Direction; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Getter; +import ch.njol.skript.util.Timespan; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; import io.papermc.paper.event.player.PlayerTradeEvent; @@ -82,6 +84,8 @@ import org.bukkit.event.block.BlockIgniteEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.block.BellRingEvent; +import org.bukkit.event.block.BellResonateEvent; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; @@ -96,6 +100,8 @@ import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; +import org.bukkit.event.entity.EntityTransformEvent; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; import org.bukkit.event.entity.FireworkExplodeEvent; import org.bukkit.event.entity.HorseJumpEvent; import org.bukkit.event.entity.ItemDespawnEvent; @@ -114,6 +120,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -708,14 +715,14 @@ public Block get(final PlayerBedLeaveEvent e) { @Override @Nullable public Block get(final PlayerBucketFillEvent e) { - return e.getBlockClicked().getRelative(e.getBlockFace()); + return e.getBlockClicked(); } }, 0); EventValues.registerEventValue(PlayerBucketFillEvent.class, Block.class, new Getter() { @Override @Nullable public Block get(final PlayerBucketFillEvent e) { - final BlockState s = e.getBlockClicked().getRelative(e.getBlockFace()).getState(); + final BlockState s = e.getBlockClicked().getState(); s.setType(Material.AIR); s.setRawData((byte) 0); return new BlockStateBlock(s, true); @@ -877,6 +884,30 @@ public Block get(PlayerMoveEvent event) { return event.getTo().clone().subtract(0, 0.5, 0).getBlock(); } }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getFrom(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getTo(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getFrom().getChunk(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getTo().getChunk(); + } + }, EventValues.TIME_NOW); // PlayerItemDamageEvent EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter() { @Override @@ -1464,21 +1495,6 @@ public TeleportCause get(final PlayerTeleportEvent e) { return e.getCause(); } }, 0); - //PlayerMoveEvent - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getFrom(); - } - }, EventValues.TIME_PAST); - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getTo(); - } - }, EventValues.TIME_NOW); //EntityMoveEvent if (Skript.classExists("io.papermc.paper.event.entity.EntityMoveEvent")) { EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter() { @@ -1666,6 +1682,22 @@ public Egg get(PlayerEggThrowEvent event) { } }, EventValues.TIME_NOW); + // PlayerStopUsingItemEvent + if (Skript.classExists("io.papermc.paper.event.player.PlayerStopUsingItemEvent")) { + EventValues.registerEventValue(PlayerStopUsingItemEvent.class, Timespan.class, new Getter() { + @Override + public Timespan get(PlayerStopUsingItemEvent event) { + return Timespan.fromTicks(event.getTicksHeldFor()); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerStopUsingItemEvent.class, ItemType.class, new Getter() { + @Override + public ItemType get(PlayerStopUsingItemEvent event) { + return new ItemType(event.getItem()); + } + }, EventValues.TIME_NOW); + } + // LootGenerateEvent if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { EventValues.registerEventValue(LootGenerateEvent.class, Entity.class, new Getter() { @@ -1753,6 +1785,90 @@ public ItemStack get(PlayerStonecutterRecipeSelectEvent event) { } }, EventValues.TIME_NOW); - } + // EntityTransformEvent + EventValues.registerEventValue(EntityTransformEvent.class, Entity[].class, new Getter() { + @Override + @Nullable + public Entity[] get(EntityTransformEvent event) { + return event.getTransformedEntities().stream().toArray(Entity[]::new); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(EntityTransformEvent.class, TransformReason.class, new Getter() { + @Override + @Nullable + public TransformReason get(EntityTransformEvent event) { + return event.getTransformReason(); + } + }, EventValues.TIME_NOW); + // BellRingEvent - these are BlockEvents and not EntityEvents, so they have declared methods for getEntity() + if (Skript.classExists("org.bukkit.event.block.BellRingEvent")) { + EventValues.registerEventValue(BellRingEvent.class, Entity.class, new Getter() { + @Override + @Nullable + public Entity get(BellRingEvent event) { + return event.getEntity(); + } + }, EventValues.TIME_NOW); + + EventValues.registerEventValue(BellRingEvent.class, Direction.class, new Getter() { + @Override + public Direction get(BellRingEvent event) { + return new Direction(event.getDirection(), 1); + } + }, EventValues.TIME_NOW); + } else if (Skript.classExists("io.papermc.paper.event.block.BellRingEvent")) { + EventValues.registerEventValue( + io.papermc.paper.event.block.BellRingEvent.class, Entity.class, + new Getter() { + @Override + @Nullable + public Entity get(io.papermc.paper.event.block.BellRingEvent event) { + return event.getEntity(); + } + }, EventValues.TIME_NOW); + } + + if (Skript.classExists("org.bukkit.event.block.BellResonateEvent")) { + EventValues.registerEventValue(BellResonateEvent.class, Entity[].class, new Getter() { + @Override + @Nullable + public Entity[] get(BellResonateEvent event) { + return event.getResonatedEntities().toArray(new LivingEntity[0]); + } + }, EventValues.TIME_NOW); + } + + // InventoryMoveItemEvent + EventValues.registerEventValue(InventoryMoveItemEvent.class, Inventory.class, new Getter() { + @Override + public Inventory get(InventoryMoveItemEvent event) { + return event.getSource(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Inventory.class, new Getter() { + @Override + public Inventory get(InventoryMoveItemEvent event) { + return event.getDestination(); + } + }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Block.class, new Getter() { + @Override + public Block get(InventoryMoveItemEvent event) { + return event.getSource().getLocation().getBlock(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Block.class, new Getter() { + @Override + public Block get(InventoryMoveItemEvent event) { + return event.getDestination().getLocation().getBlock(); + } + }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(InventoryMoveItemEvent.class, ItemStack.class, new Getter() { + @Override + public ItemStack get(InventoryMoveItemEvent event) { + return event.getItem(); + } + }, 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 7f96bab14d8..e59a5678403 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -24,27 +24,26 @@ import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.entity.BoatChestData; import ch.njol.skript.entity.BoatData; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.RabbitData; -import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.Date; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Experience; -import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.GameruleValue; import ch.njol.skript.util.StructureType; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timeperiod; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.slot.EquipmentSlot; import ch.njol.skript.util.slot.Slot; import ch.njol.skript.util.slot.SlotWithIndex; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; @@ -61,6 +60,8 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; import java.util.Objects; @@ -630,6 +631,28 @@ public boolean supportsOrdering() { return false; } }); + + // Location - Location + Comparators.registerComparator(Location.class, Location.class, new Comparator() { + @Override + public Relation compare(Location first, Location second) { + return Relation.get( + // compare worlds + Objects.equals(first.getWorld(), second.getWorld()) && + // compare xyz coords + first.toVector().equals(second.toVector()) && + // normalize yaw and pitch to [-180, 180) and [-90, 90] respectively + // before comparing them + Location.normalizeYaw(first.getYaw()) == Location.normalizeYaw(second.getYaw()) && + Location.normalizePitch(first.getPitch()) == Location.normalizePitch(second.getPitch()) + ); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); } } 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 a8b2688e94e..8bd6dc906f8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -158,14 +158,14 @@ public DefaultConverters() {} if (holder instanceof DoubleChest) return holder.getInventory().getLocation().getBlock(); return null; - }); + }, Converter.NO_CHAINING); // InventoryHolder - Entity Converters.registerConverter(InventoryHolder.class, Entity.class, holder -> { if (holder instanceof Entity) return (Entity) holder; return null; - }); + }, Converter.NO_CHAINING); // Enchantment - EnchantmentType Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); 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 ed95a1a4bc5..e2c5392dd39 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -336,7 +336,7 @@ public Number[] executeSimple(Object[][] params) { "clamp(5, 7, 10) = 7", "clamp((5, 0, 10, 9, 13), 7, 10) = (7, 7, 10, 9, 10)", "set {_clamped::*} to clamp({_values::*}, 0, 10)") - .since("INSERT VERSION"); + .since("2.8.0"); // misc @@ -376,10 +376,28 @@ public Location[] execute(FunctionEvent e, Object[][] params) { } }.description("Creates a location from a world and 3 coordinates, with an optional yaw and pitch.", "If for whatever reason the world is not found, it will fallback to the server's main world.") - .examples("location(0, 128, 0)", - "location(player's x-coordinate, player's y-coordinate + 5, player's z-coordinate, player's world, 0, 90)", - "location(0, 64, 0, world \"world_nether\")", - "location(100, 110, -145, world(\"my_custom_world\"))") + .examples("# TELEPORTING", + "teleport player to location(1,1,1, world \"world\")", + "teleport player to location(1,1,1, world \"world\", 100, 0)", + "teleport player to location(1,1,1, world \"world\", yaw of player, pitch of player)", + "teleport player to location(1,1,1, world of player)", + "teleport player to location(1,1,1, world(\"world\"))", + "teleport player to location({_x}, {_y}, {_z}, {_w}, {_yaw}, {_pitch})", + "# SETTING BLOCKS", + "set block at location(1,1,1, world \"world\") to stone", + "set block at location(1,1,1, world \"world\", 100, 0) to stone", + "set block at location(1,1,1, world of player) to stone", + "set block at location(1,1,1, world(\"world\")) to stone", + "set block at location({_x}, {_y}, {_z}, {_w}) to stone", + "# USING VARIABLES", + "set {_l1} to location(1,1,1)", + "set {_l2} to location(10,10,10)", + "set blocks within {_l1} and {_l2} to stone", + "if player is within {_l1} and {_l2}:", + "# OTHER", + "kill all entities in radius 50 around location(1,65,1, world \"world\")", + "delete all entities in radius 25 around location(50,50,50, world \"world_nether\")", + "ignite all entities in radius 25 around location(1,1,1, world of player)") .since("2.2")); Functions.registerFunction(new SimpleJavaFunction("date", new Parameter[] { @@ -522,7 +540,7 @@ public Player[] executeSimple(Object[][] params) { } }).description("Returns an online player from their name or UUID, if player is offline function will return nothing.", "Setting 'getExactPlayer' parameter to true will return the player whose name is exactly equal to the provided name instead of returning a player that their name starts with the provided name.") .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("INSERT VERSION"); + .since("2.8.0"); Functions.registerFunction(new SimpleJavaFunction("offlineplayer", new Parameter[] { new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null) @@ -540,7 +558,16 @@ public OfflinePlayer[] executeSimple(Object[][] params) { } }).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("INSERT VERSION"); + .since("2.8.0"); + + Functions.registerFunction(new SimpleJavaFunction("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean[] executeSimple(Object[][] params) { + return new Boolean[] {Double.isNaN(((Number) params[0][0]).doubleValue())}; + } + }).description("Returns true if the input is NaN (not a number).") + .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") + .since("2.8.0"); } } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java new file mode 100644 index 00000000000..e6397a1e767 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -0,0 +1,120 @@ +/** + * 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.classes.data; + +import ch.njol.skript.util.Date; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Utils; +import ch.njol.util.Math2; +import org.bukkit.util.Vector; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.Operator; + +public class DefaultOperations { + + static { + // Number - Number + Arithmetics.registerOperation(Operator.ADDITION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() + right.longValue(); + return left.doubleValue() + right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.SUBTRACTION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() - right.longValue(); + return left.doubleValue() - right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.MULTIPLICATION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() * right.longValue(); + return left.doubleValue() * right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); + Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right) && right.longValue() >= 0) + return (long) Math.pow(left.longValue(), right.longValue()); + return Math.pow(left.doubleValue(), right.doubleValue()); + }); + Arithmetics.registerDifference(Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return Math.abs(left.longValue() - right.longValue()); + return Math.abs(left.doubleValue() - right.doubleValue()); + }); + Arithmetics.registerDefaultValue(Number.class, () -> 0L); + + // Vector - Vector + Arithmetics.registerOperation(Operator.ADDITION, Vector.class, (left, right) -> left.clone().add(right)); + Arithmetics.registerOperation(Operator.SUBTRACTION, Vector.class, (left, right) -> left.clone().subtract(right)); + Arithmetics.registerOperation(Operator.MULTIPLICATION, Vector.class, (left, right) -> left.clone().multiply(right)); + Arithmetics.registerOperation(Operator.DIVISION, Vector.class, (left, right) -> left.clone().divide(right)); + Arithmetics.registerDifference(Vector.class, + (left, right) -> new Vector(Math.abs(left.getX() - right.getX()), Math.abs(left.getY() - right.getY()), Math.abs(left.getZ() - right.getZ()))); + Arithmetics.registerDefaultValue(Vector.class, Vector::new); + + // Vector - Number + // Number - Vector + Arithmetics.registerOperation(Operator.MULTIPLICATION, Vector.class, Number.class, (left, right) -> left.clone().multiply(right.doubleValue()), (left, right) -> { + double number = left.doubleValue(); + Vector leftVector = new Vector(number, number, number); + return leftVector.multiply(right); + }); + Arithmetics.registerOperation(Operator.DIVISION, Vector.class, Number.class, (left, right) -> { + double number = right.doubleValue(); + Vector rightVector = new Vector(number, number, number); + return left.clone().divide(rightVector); + }, (left, right) -> { + double number = left.doubleValue(); + Vector leftVector = new Vector(number, number, number); + return leftVector.divide(right); + }); + + // Timespan - Timespan + Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(Math2.addClamped(left.getMilliSeconds(), right.getMilliSeconds()))); + Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, (left, right) -> new Timespan(Math.max(0, left.getMilliSeconds() - right.getMilliSeconds()))); + Arithmetics.registerDifference(Timespan.class, (left, right) -> new Timespan(Math.abs(left.getMilliSeconds() - right.getMilliSeconds()))); + Arithmetics.registerDefaultValue(Timespan.class, Timespan::new); + + // Timespan - Number + // Number - Timespan + Arithmetics.registerOperation(Operator.MULTIPLICATION, Timespan.class, Number.class, (left, right) -> { + long scalar = right.longValue(); + if (scalar < 0) + return null; + return new Timespan(Math2.multiplyClamped(left.getMilliSeconds(), scalar)); + }, (left, right) -> { + long scalar = left.longValue(); + if (scalar < 0) + return null; + return new Timespan(scalar * right.getMilliSeconds()); + }); + Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Number.class, (left, right) -> { + long scalar = right.longValue(); + if (scalar <= 0) + return null; + return new Timespan(left.getMilliSeconds() / scalar); + }); + + // Date - Timespan + Arithmetics.registerOperation(Operator.ADDITION, Date.class, Timespan.class, Date::plus); + Arithmetics.registerOperation(Operator.SUBTRACTION, Date.class, Timespan.class, Date::minus); + Arithmetics.registerDifference(Date.class, Timespan.class, Date::difference); + + } + +} diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 1f463e87361..ce726218e6f 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -20,7 +20,6 @@ import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.NumberArithmetic; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; import ch.njol.skript.lang.ParseContext; @@ -125,7 +124,7 @@ public Number deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Long.class, "long") .user("int(eger)?s?") @@ -184,7 +183,7 @@ public Long deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Integer.class, "integer") .name(ClassInfo.NO_DOC) @@ -241,7 +240,7 @@ public Integer deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Double.class, "double") .name(ClassInfo.NO_DOC) @@ -303,7 +302,7 @@ public Double deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Float.class, "float") .name(ClassInfo.NO_DOC) @@ -364,7 +363,7 @@ public Float deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Boolean.class, "boolean") .user("booleans?") @@ -486,7 +485,7 @@ public Short deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Byte.class, "byte") .name(ClassInfo.NO_DOC) @@ -543,7 +542,7 @@ public Byte deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(String.class, "string") .user("(text|string)s?") @@ -551,7 +550,7 @@ public boolean mustSyncDeserialization() { .description("Text is simply text, i.e. a sequence of characters, which can optionally contain expressions which will be replaced with a meaningful representation " + "(e.g. %player% will be replaced with the player's name).", "Because scripts are also text, you have to put text into double quotes to tell Skript which part of the line is an effect/expression and which part is the text.", - "Please read the article on Texts and Variable Names to learn more.") + "Please read the article on Texts and Variable Names to learn more.") .usage("simple: \"...\"", "quotes: \"...\"\"...\"", "expressions: \"...%expression%...\"", @@ -576,6 +575,7 @@ public String parse(String s, ParseContext context) { return Utils.replaceChatStyles("" + s.substring(1, s.length() - 1).replace("\"\"", "\"")); return null; case COMMAND: + case PARSE: return s; } assert false; diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 55727c673a6..39dc6447fa5 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -34,7 +34,6 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.EnchantmentUtils; import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.EnumSerializer; @@ -315,38 +314,7 @@ public String toString(final Timespan t, final int flags) { public String toVariableNameString(final Timespan o) { return "timespan:" + o.getMilliSeconds(); } - }).serializer(new YggdrasilSerializer<>()) - .math(Timespan.class, new Arithmetic() { - @Override - public Timespan difference(final Timespan t1, final Timespan t2) { - return new Timespan(Math.abs(t1.getMilliSeconds() - t2.getMilliSeconds())); - } - - @Override - public Timespan add(final Timespan value, final Timespan difference) { - return new Timespan(value.getMilliSeconds() + difference.getMilliSeconds()); - } - - @Override - public Timespan subtract(final Timespan value, final Timespan difference) { - return new Timespan(Math.max(0, value.getMilliSeconds() - difference.getMilliSeconds())); - } - - @Override - public Timespan multiply(Timespan value, Timespan multiplier) { - throw new UnsupportedOperationException(); - } - - @Override - public Timespan divide(Timespan value, Timespan divider) { - throw new UnsupportedOperationException(); - } - - @Override - public Timespan power(Timespan value, Timespan exponent) { - throw new UnsupportedOperationException(); - } - })); + }).serializer(new YggdrasilSerializer<>())); // TODO remove Classes.registerClass(new ClassInfo<>(Timeperiod.class, "timeperiod") @@ -408,38 +376,7 @@ public String toVariableNameString(final Timeperiod o) { "subtract a day from {_yesterday}", "# now {_yesterday} represents the date 24 hours before now") .since("1.4") - .serializer(new YggdrasilSerializer<>()) - .math(Timespan.class, new Arithmetic() { - @Override - public Timespan difference(final Date first, final Date second) { - return first.difference(second); - } - - @Override - public Date add(final Date value, final Timespan difference) { - return new Date(value.getTimestamp() + difference.getMilliSeconds()); - } - - @Override - public Date subtract(final Date value, final Timespan difference) { - return new Date(value.getTimestamp() - difference.getMilliSeconds()); - } - - @Override - public Date multiply(Date value, Timespan multiplier) { - throw new UnsupportedOperationException(); - } - - @Override - public Date divide(Date value, Timespan divider) { - throw new UnsupportedOperationException(); - } - - @Override - public Date power(Date value, Timespan exponent) { - throw new UnsupportedOperationException(); - } - })); + .serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Direction.class, "direction") .user("directions?") diff --git a/src/main/java/ch/njol/skript/command/CommandHelp.java b/src/main/java/ch/njol/skript/command/CommandHelp.java index 8fcf6f0a3b4..ca1c82f5e08 100644 --- a/src/main/java/ch/njol/skript/command/CommandHelp.java +++ b/src/main/java/ch/njol/skript/command/CommandHelp.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.Map.Entry; import org.bukkit.command.CommandSender; @@ -33,118 +34,126 @@ import ch.njol.skript.localization.Message; import ch.njol.skript.util.SkriptColor; -/** - * @author Peter Güttinger - */ public class CommandHelp { - + private final static String DEFAULTENTRY = "description"; - + private final static ArgsMessage m_invalid_argument = new ArgsMessage("commands.invalid argument"); private final static Message m_usage = new Message("skript command.usage"); - + + private final String actualCommand, actualNode, argsColor; private String command; + private String langNode; + + private boolean revalidate = true; @Nullable private Message description = null; - private final String argsColor; - - @Nullable - private String langNode = null; - - private final LinkedHashMap arguments = new LinkedHashMap<>(); - + + private final Map arguments = new LinkedHashMap<>(); + @Nullable - private Message wildcardArg = null; - - public CommandHelp(final String command, final SkriptColor argsColor, final String langNode) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); - this.langNode = langNode; - description = new Message(langNode + "." + DEFAULTENTRY); + private ArgumentHolder wildcardArg = null; + + public CommandHelp(String command, SkriptColor argsColor, String langNode) { + this(command, argsColor.getFormattedChat(), langNode); } - - public CommandHelp(final String command, final SkriptColor argsColor) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); + + public CommandHelp(String command, SkriptColor argsColor) { + this(command, argsColor.getFormattedChat(), command); } - - public CommandHelp add(final String argument) { - if (langNode == null) { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - arguments.put(carg, argument); - } else { - arguments.put(argument, null); - } - } else { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - wildcardArg = new Message(langNode + "." + argument); - arguments.put(carg, wildcardArg); - } else { - arguments.put(argument, new Message(langNode + "." + argument)); - } + + private CommandHelp(String command, String argsColor, String node) { + this.actualCommand = this.command = command; + this.actualNode = this.langNode = node; + this.argsColor = argsColor; + } + + public CommandHelp add(String argument) { + ArgumentHolder holder = new ArgumentHolder(argument); + if (argument.startsWith("<") && argument.endsWith(">")) { + argument = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; + wildcardArg = holder; } + arguments.put(argument, holder); return this; } - - public CommandHelp add(final CommandHelp help) { + + public CommandHelp add(CommandHelp help) { arguments.put(help.command, help); help.onAdd(this); return this; } - - protected void onAdd(final CommandHelp parent) { - langNode = parent.langNode + "." + command; - description = new Message(langNode + "." + DEFAULTENTRY); - command = parent.command + " " + parent.argsColor + command; - for (final Entry e : arguments.entrySet()) { - if (e.getValue() instanceof CommandHelp) { - ((CommandHelp) e.getValue()).onAdd(this); - } else { - if (e.getValue() != null) { // wildcard arg - wildcardArg = new Message(langNode + "." + e.getValue()); - e.setValue(wildcardArg); - } else { - e.setValue(new Message(langNode + "." + e.getKey())); - } + + protected void onAdd(CommandHelp parent) { + langNode = parent.langNode + "." + actualNode; + command = parent.command + " " + parent.argsColor + actualCommand; + revalidate = true; + for (Entry entry : arguments.entrySet()) { + if (entry.getValue() instanceof CommandHelp) { + ((CommandHelp) entry.getValue()).onAdd(this); + continue; } + ((ArgumentHolder) entry.getValue()).revalidate = true; } } - - public boolean test(final CommandSender sender, final String[] args) { + + public boolean test(CommandSender sender, String[] args) { return test(sender, args, 0); } - - private boolean test(final CommandSender sender, final String[] args, final int index) { + + private boolean test(CommandSender sender, String[] args, int index) { if (index >= args.length) { showHelp(sender); return false; } - final Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); + Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); if (help == null && wildcardArg == null) { showHelp(sender, m_invalid_argument.toString(argsColor + args[index])); return false; } - if (help instanceof CommandHelp) - return ((CommandHelp) help).test(sender, args, index + 1); - return true; + return !(help instanceof CommandHelp) || ((CommandHelp) help).test(sender, args, index + 1); } - - public void showHelp(final CommandSender sender) { + + public void showHelp(CommandSender sender) { showHelp(sender, m_usage.toString()); } - - private void showHelp(final CommandSender sender, final String pre) { + + private void showHelp(CommandSender sender, String pre) { Skript.message(sender, pre + " " + command + " " + argsColor + "..."); - for (final Entry e : arguments.entrySet()) { - Skript.message(sender, " " + argsColor + e.getKey() + " " + GRAY + "-" + RESET + " " + e.getValue()); - } + for (Entry entry : arguments.entrySet()) + Skript.message(sender, " " + argsColor + entry.getKey() + " " + GRAY + "-" + RESET + " " + entry.getValue()); } - + @Override public String toString() { + if (revalidate) { + // We don't want to create a new Message object each time toString is called + description = new Message(langNode + "." + DEFAULTENTRY); + revalidate = false; + } return "" + description; } - + + private class ArgumentHolder { + + private final String argument; + private boolean revalidate = true; + @Nullable + private Message description = null; + + private ArgumentHolder(String argument) { + this.argument = argument; + } + + @Override + public String toString() { + if (revalidate) { + description = new Message(langNode + "." + argument); + revalidate = false; + } + return "" + description; + } + + } + } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 35e12057bb1..b8c83ccab68 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -41,6 +41,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; @@ -68,7 +69,7 @@ */ @SuppressWarnings("deprecation") public abstract class Commands { - + public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments"); public final static Message m_internal_error = new Message("commands.internal error"); public final static Message m_correct_usage = new Message("commands.correct usage"); @@ -79,26 +80,26 @@ public abstract class Commands { public static final int CONVERTER_NO_COMMAND_ARGUMENTS = 4; private final static Map commands = new HashMap<>(); - + @Nullable private static SimpleCommandMap commandMap = null; @Nullable private static Map cmKnownCommands; @Nullable private static Set cmAliases; - + static { init(); // separate method for the annotation } public static Set getScriptCommands(){ return commands.keySet(); } - + @Nullable public static SimpleCommandMap getCommandMap(){ return commandMap; } - + @SuppressWarnings("unchecked") private static void init() { try { @@ -106,11 +107,11 @@ private static void init() { Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map) knownCommandsField.get(commandMap); - + try { Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); @@ -125,24 +126,42 @@ private static void init() { commandMap = null; } } - + @Nullable public static List> currentArguments = null; - + @SuppressWarnings("null") private final static Pattern escape = Pattern.compile("[" + Pattern.quote("(|)<>%\\") + "]"); @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - + public static String escape(String string) { return "" + escape.matcher(string).replaceAll("\\\\$0"); } - + public static String unescape(String string) { return "" + unescape.matcher(string).replaceAll("$0"); } - + private final static Listener commandListener = new Listener() { + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + // Spigot will simply report that the command doesn't exist if a player does not have permission to use it. + // This is good security but, well, it's a breaking change for Skript. So we need to check for permissions + // ourselves and handle those messages, for every command. + + // parse command, see if it's a skript command + String[] cmd = event.getMessage().substring(1).split("\\s+", 2); + String label = cmd[0].toLowerCase(Locale.ENGLISH); + String arguments = cmd.length == 1 ? "" : "" + cmd[1]; + ScriptCommand command = commands.get(label); + + // if so, check permissions + if (command != null && !command.checkPermissions(event.getPlayer(), label, arguments)) + event.setCancelled(true); + } + @SuppressWarnings("null") @EventHandler(priority = EventPriority.HIGHEST) public void onServerCommand(ServerCommandEvent event) { @@ -154,7 +173,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())) return false; @@ -170,7 +189,7 @@ static boolean handleEffectCommand(CommandSender sender, String command) { parserInstance.setCurrentEvent("effect command", EffectCommandEvent.class); Effect effect = Effect.parse(command, null); parserInstance.deleteCurrentEvent(); - + if (effect != null) { log.clear(); // ignore warnings and stuff log.printLog(); @@ -219,7 +238,7 @@ public static boolean scriptCommandExists(String command) { ScriptCommand scriptCommand = commands.get(command); return scriptCommand != null && scriptCommand.getName().equals(command); } - + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates ScriptCommand existingCommand = commands.get(command.getLabel()); @@ -230,7 +249,7 @@ public static void registerCommand(ScriptCommand command) { ); return; } - + if (commandMap != null) { assert cmKnownCommands != null;// && cmAliases != null; command.register(commandMap, cmKnownCommands, cmAliases); @@ -262,9 +281,9 @@ public static void unregisterCommand(ScriptCommand scriptCommand) { } commands.values().removeIf(command -> command == scriptCommand); } - + private static boolean registeredListeners = false; - + public static void registerListeners() { if (!registeredListeners) { Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance()); @@ -298,15 +317,15 @@ public void onPlayerChat(AsyncPlayerChatEvent event) { registeredListeners = true; } } - + /** * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic) */ public static final class CommandAliasHelpTopic extends HelpTopic { - + private final String aliasFor; private final HelpMap helpMap; - + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; @@ -314,7 +333,7 @@ public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { Validate.isTrue(!name.equals(this.aliasFor), "Command " + name + " cannot be alias for itself"); shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor; } - + @Override @NotNull public String getFullText(CommandSender forWho) { @@ -326,7 +345,7 @@ public String getFullText(CommandSender forWho) { } return "" + fullText; } - + @Override public boolean canSee(CommandSender commandSender) { if (amendedPermission != null) @@ -335,5 +354,5 @@ public boolean canSee(CommandSender commandSender) { return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } - + } diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 2ce33ac7152..05d01680cc3 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -117,7 +117,7 @@ public class ScriptCommand implements TabExecutor { /** * Creates a new SkriptCommand. - * + * * @param name /name * @param pattern * @param arguments the list of Arguments this command takes @@ -237,16 +237,8 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender, commandLabel, rest); - if (!permission.isEmpty() && !sender.hasPermission(permission)) { - if (sender instanceof Player) { - List components = - permissionMessage.getMessageComponents(event); - ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); - } else { - sender.sendMessage(permissionMessage.getSingle(event)); - } + if (!checkPermissions(sender, event)) return false; - } cooldownCheck : { if (sender instanceof Player && cooldown != null) { @@ -316,19 +308,37 @@ boolean execute2(final ScriptCommandEvent event, final CommandSender sender, fin } finally { log.stop(); } - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# /" + name + " " + rest); final long startTrigger = System.nanoTime(); - + if (!trigger.execute(event)) sender.sendMessage(Commands.m_internal_error.toString()); - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# " + name + " took " + 1. * (System.nanoTime() - startTrigger) / 1000000. + " milliseconds"); return true; } + public boolean checkPermissions(CommandSender sender, String commandLabel, String arguments) { + return checkPermissions(sender, new ScriptCommandEvent(this, sender, commandLabel, arguments)); + } + + public boolean checkPermissions(CommandSender sender, Event event) { + if (!permission.isEmpty() && !sender.hasPermission(permission)) { + if (sender instanceof Player) { + List components = + permissionMessage.getMessageComponents(event); + ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); + } else { + sender.sendMessage(permissionMessage.getSingle(event)); + } + return false; + } + return true; + } + public void sendHelp(final CommandSender sender) { if (!description.isEmpty()) sender.sendMessage(description); @@ -337,7 +347,7 @@ public void sendHelp(final CommandSender sender) { /** * Gets the arguments this command takes. - * + * * @return The internal list of arguments. Do not modify it! */ public List> getArguments() { @@ -575,7 +585,7 @@ public List onTabComplete(@Nullable CommandSender sender, @Nullable Comm Class argType = arg.getType(); if (argType.equals(Player.class) || argType.equals(OfflinePlayer.class)) return null; // Default completion - + return Collections.emptyList(); // No tab completion here! } diff --git a/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java index d5a0aaaa591..07f5735e4f9 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java +++ b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java @@ -35,7 +35,7 @@ "\tif player can't pick up items:", "\t\tsend \"Be careful, you won't be able to pick that up!\" to player" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondCanPickUpItems extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index 707f2626e83..f725c1bd6f8 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.conditions; +import ch.njol.skript.log.ParseLogHandler; +import ch.njol.skript.util.Time; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -52,6 +54,7 @@ import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.skriptlang.skript.lang.util.Cyclical; @Name("Comparison") @Description({"A very general condition, it simply compares two values. Usually you can only compare for equality (e.g. block is/isn't of <type>), " + @@ -164,9 +167,8 @@ public static String f(final Expression e) { @SuppressWarnings("unchecked") private boolean init(String expr) { - RetainingLogHandler log = SkriptLogger.startRetainingLog(); Expression third = this.third; - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { if (first.getReturnType() == Object.class) { Expression expression = null; if (first instanceof UnparsedLiteral) @@ -174,7 +176,7 @@ private boolean init(String expr) { if (expression == null) expression = first.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } first = expression; @@ -186,7 +188,7 @@ private boolean init(String expr) { if (expression == null) expression = second.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } second = expression; @@ -198,14 +200,13 @@ private boolean init(String expr) { if (expression == null) expression = third.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } this.third = third = expression; } - log.printLog(); - } finally { - log.stop(); + // we do not want to print any errors as they are not applicable + log.printLog(false); } Class firstReturnType = first.getReturnType(); Class secondReturnType = third == null ? second.getReturnType() : Utils.getSuperType(second.getReturnType(), third.getReturnType()); @@ -351,15 +352,29 @@ public boolean check(final Event e) { return third.check(e, (Checker) o3 -> { boolean isBetween; if (comparator != null) { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + if (o1 instanceof Cyclical && o2 instanceof Cyclical && o3 instanceof Cyclical) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + } } else { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + if (o1 instanceof Cyclical && o2 instanceof Cyclical && o3 instanceof Cyclical) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + } } return relation == Relation.NOT_EQUAL ^ isBetween; }); diff --git a/src/main/java/ch/njol/skript/conditions/CondGlowingText.java b/src/main/java/ch/njol/skript/conditions/CondGlowingText.java new file mode 100644 index 00000000000..aaf14939305 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondGlowingText.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.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import org.bukkit.block.Block; + +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.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; + +@Name("Has Glowing Text") +@Description("Checks whether a sign (either a block or an item) has glowing text") +@Examples("if target block has glowing text") +@Since("2.8.0") +public class CondGlowingText extends PropertyCondition { + + static { + if (Skript.methodExists(Sign.class, "isGlowingText")) + register(CondGlowingText.class, PropertyType.HAVE, "glowing text", "blocks/itemtypes"); + } + + @Override + public boolean check(Object obj) { + if (obj instanceof Block) { + BlockState state = ((Block) obj).getState(); + return state instanceof Sign && ((Sign) state).isGlowingText(); + } else if (obj instanceof ItemType) { + ItemMeta meta = ((ItemType) obj).getItemMeta(); + if (meta instanceof BlockStateMeta) { + BlockState state = ((BlockStateMeta) meta).getBlockState(); + return state instanceof Sign && ((Sign) state).isGlowingText(); + } + } + return false; + } + + @Override + protected PropertyType getPropertyType() { + return PropertyType.HAVE; + } + + @Override + protected String getPropertyName() { + return "glowing text"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java index be79e48220b..77902e88026 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java @@ -40,7 +40,7 @@ "if player has player's tool on cooldown:", "\tsend \"You can't use this item right now. Wait %item cooldown of player's tool for player%\"" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondHasItemCooldown extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java index dd4a18f9ab0..74f820b5613 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java @@ -40,7 +40,7 @@ "victim has line of sight to attacker", "player has no line of sight to location 100 blocks in front of player" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondHasLineOfSight extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java index af56f90379f..48cdfe5eb40 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java @@ -37,7 +37,7 @@ "\tmessage\"The spider is now climbing!\"" }) @RequiredPlugins("Minecraft 1.17+") -@Since("INSERT VERSION") +@Since("2.8.0") public class CondIsClimbing extends PropertyCondition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java new file mode 100644 index 00000000000..d6030bf2b6a --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java @@ -0,0 +1,97 @@ +/** + * 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.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.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.EquipmentSlot; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Hand Raised") +@Description({ + "Checks whether an entity has one or both of their hands raised.", + "Hands are raised when an entity is using an item (eg: blocking, drawing a bow, eating)." +}) +@Examples({ + "on damage of player:", + "\tif victim's main hand is raised:", + "\t\tdrop player's tool at player", + "\t\tset player's tool to air" +}) +@Since("2.8.0") +@RequiredPlugins("Paper") +public class CondIsHandRaised extends Condition { + + static { + Skript.registerCondition(CondIsHandRaised.class, + "%livingentities%'[s] [:main] hand[s] (is|are) raised", + "%livingentities%'[s] [:main] hand[s] (isn't|is not|aren't|are not) raised", + "[:main] hand[s] of %livingentities% (is|are) raised", + "[:main] hand[s] of %livingentities% (isn't|is not|aren't|are not) raised", + + "%livingentities%'[s] off[ |-]hand[s] (is|are) raised", + "%livingentities%'[s] off[ |-]hand[s] (isn't|is not|aren't|are not) raised", + "off[ |-]hand[s] of %livingentities% (is|are) raised", + "off[ |-]hand[s] of %livingentities% (isn't|is not|aren't|are not) raised" + ); + } + + private Expression entities; + @Nullable + private EquipmentSlot hand; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression) exprs[0]; + setNegated(matchedPattern % 2 == 1); + if (matchedPattern >= 4) { + hand = EquipmentSlot.OFF_HAND; + } else if (parseResult.hasTag("main")) { + hand = EquipmentSlot.HAND; + } + return true; + } + + @Override + public boolean check(Event event) { + // True if hand is raised AND hand matches the hand we're checking for (null for both) + return entities.check(event, livingEntity -> + livingEntity.isHandRaised() && ((hand == null) || livingEntity.getHandRaised().equals(hand)), + isNegated() + ); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return entities.toString(event, debug) + "'s " + (hand == null ? "" : (hand == EquipmentSlot.HAND ? "main " : "off ")) + "hand" + + (entities.isSingle() ? " is" : "s are") + (isNegated() ? " not " : "") + " raised"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInvulnerable.java b/src/main/java/ch/njol/skript/conditions/CondIsInvulnerable.java index 7dd7a1aec94..5fa5be0c106 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsInvulnerable.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsInvulnerable.java @@ -33,7 +33,7 @@ public class CondIsInvulnerable extends PropertyCondition { static { - register(CondIsInvulnerable.class, PropertyType.BE, "invulnerable", "entities"); + register(CondIsInvulnerable.class, PropertyType.BE, "(invulnerable|invincible)", "entities"); } @Override diff --git a/src/main/java/ch/njol/skript/conditions/CondIsJumping.java b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java index 9a8c1124fd3..fb3738a05c6 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsJumping.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java @@ -40,7 +40,7 @@ "\t\twait 5 ticks", "\tpush event-entity upwards" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.15+") public class CondIsJumping extends PropertyCondition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java index ab2f4b2e26b..7101ad18b92 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java @@ -43,7 +43,7 @@ "\tif victim is left handed:", "\t\tcancel event" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.17.1+ (entities)") public class CondIsLeftHanded extends PropertyCondition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java new file mode 100644 index 00000000000..3fb40549421 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java @@ -0,0 +1,105 @@ +/** + * 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.conditions.base.PropertyCondition; +import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; +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 com.destroystokyo.paper.entity.Pathfinder; +import com.destroystokyo.paper.entity.Pathfinder.PathResult; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Pathfinding") +@Description({ + "Checks whether living entities are pathfinding.", + "Can only be a living entity that is a Mob." +}) +@Examples({ + "make {_entity} pathfind to {_location} at speed 2", + "while {_entity} is pathfinding", + "\twait a second", + "launch flickering trailing burst firework colored red at location of {_entity}", + "subtract 10 from {defence::tower::health}", + "clear entity within {_entity}" +}) +@RequiredPlugins("Paper") +@Since("INSERT VERSION") +public class CondIsPathfinding extends Condition { + + static { + if (Skript.classExists("org.bukkit.entity.Mob") && Skript.methodExists(Mob.class, "getPathfinder")) + PropertyCondition.register(CondIsPathfinding.class, "pathfinding [to[wards] %-livingentity/location%]", "livingentities"); + } + + private Expression entities; + private Expression target; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression) expressions[0]; + target = expressions[1]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + return entities.check(event, entity -> { + if (!(entity instanceof Mob)) + return false; + Pathfinder pathfind = ((Mob) entity).getPathfinder(); + if (target == null) + return pathfind.hasPath(); + + PathResult current = pathfind.getCurrentPath(); + Object target = this.target.getSingle(event); + if (target == null || current == null) + return false; + Location location = current.getFinalPoint(); + if (target instanceof Location) + return location.equals(target); + assert target instanceof LivingEntity; + LivingEntity entityTarget = (LivingEntity) target; + return location.distance(((Mob) entityTarget).getLocation()) < 1; + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return PropertyCondition.toString(this, PropertyType.BE, event, debug, entities, "pathfinding" + + target == null ? "" : " to " + target.toString(event, debug)); + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsResonating.java b/src/main/java/ch/njol/skript/conditions/CondIsResonating.java new file mode 100644 index 00000000000..c2614aaead1 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsResonating.java @@ -0,0 +1,58 @@ +/** + * 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.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.RequiredPlugins; +import ch.njol.skript.doc.Since; +import org.bukkit.block.Bell; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; + +@Name("Bell Is Resonating") +@Description({ + "Checks to see if a bell is currently resonating.", + "A bell will start resonating five game ticks after being rung, and will continue to resonate for 40 game ticks." +}) +@Examples("target block is resonating") +@RequiredPlugins("Spigot 1.19.4+") +@Since("INSERT VERSION") +public class CondIsResonating extends PropertyCondition { + + static { + if (Skript.classExists("org.bukkit.block.Bell") && Skript.methodExists(Bell.class, "isResonating")) + register(CondIsResonating.class, "resonating", "blocks"); + } + + @Override + public boolean check(Block value) { + BlockState state = value.getState(false); + return state instanceof Bell && ((Bell) state).isResonating(); + } + + @Override + protected String getPropertyName() { + return "resonating"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsRinging.java b/src/main/java/ch/njol/skript/conditions/CondIsRinging.java new file mode 100644 index 00000000000..c7ddca50b1a --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsRinging.java @@ -0,0 +1,55 @@ +/** + * 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.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.RequiredPlugins; +import ch.njol.skript.doc.Since; +import org.bukkit.block.Bell; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; + +@Name("Bell Is Ringing") +@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") +public class CondIsRinging extends PropertyCondition { + + static { + if (Skript.classExists("org.bukkit.block.Bell") && Skript.methodExists(Bell.class, "isShaking")) + register(CondIsRinging.class, "ringing", "blocks"); + } + + @Override + public boolean check(Block value) { + BlockState state = value.getState(false); + return state instanceof Bell && ((Bell) state).isShaking(); + } + + @Override + protected String getPropertyName() { + return "ringing"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSheared.java b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java index f667f292899..a000891be95 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsSheared.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java @@ -38,7 +38,7 @@ "if targeted entity of player is sheared:", "\tsend \"This entity has nothing left to shear!\" to player" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("MC 1.13+ (cows, sheep & snowmen), Paper 1.19.4+ (all shearable entities)") public class CondIsSheared extends PropertyCondition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java index 71dbbea2dcc..1b71258482b 100755 --- a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java @@ -35,7 +35,7 @@ @Name("Is Slime Chunk") @Description({"Tests whether a chunk is a so-called slime chunk.", "Slimes can generally spawn in the swamp biome and in slime chunks.", - "For more info, see the Minecraft wiki."}) + "For more info, see the Minecraft wiki."}) @Examples({"command /slimey:", "\ttrigger:", "\t\tif chunk at player is a slime chunk:", diff --git a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java similarity index 96% rename from src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java rename to src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java index 24a47479a49..e2888c0cf80 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java +++ b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java @@ -16,9 +16,8 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.test.runner; +package ch.njol.skript.conditions; -import ch.njol.skript.doc.NoDoc; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -37,7 +36,6 @@ @Description("Checks if current Minecraft version is given version or newer.") @Examples("running minecraft \"1.14\"") @Since("2.5") -@NoDoc public class CondMinecraftVersion extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java b/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java deleted file mode 100644 index 08becff95c3..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java +++ /dev/null @@ -1,51 +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.conditions; - -import org.bukkit.entity.Projectile; - -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; - -@Name("Projectile Can Bounce") -@Description("Whether or not a projectile can bounce.") -@Examples({"on shoot:", - "\tsend \"Boing!\" to all players if projectile can bounce"}) -@Since("2.5.1") -public class CondProjectileCanBounce extends PropertyCondition { - - static { - register(CondProjectileCanBounce.class, PropertyType.CAN, "bounce", "projectiles"); - } - - @Override - public boolean check(Projectile projectile) { - return projectile.doesBounce(); - } - - @Override - public String getPropertyName() { - return "bounce"; - } - -} diff --git a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java index 7a99c659d5d..528b216ffc7 100644 --- a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java +++ b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java @@ -53,7 +53,7 @@ * the first one needs to be a non-negated one and a negated one. */ public abstract class PropertyCondition extends Condition implements Checker { - + /** * See {@link PropertyCondition} for more info */ @@ -74,100 +74,114 @@ public enum PropertyType { * Indicates that the condition is in a form of something has/have something, * also possibly in the negated form */ - HAVE + HAVE, + + /** + * Indicates that the condition is in a form of something will/be something, + * also possibly in the negated form + */ + WILL } - - @SuppressWarnings("null") + private Expression expr; - + /** - * @param c the class to register + * @param condition the class to register * @param property the property name, for example fly in players can fly * @param type must be plural, for example players in players can fly */ - public static void register(final Class c, final String property, final String type) { - register(c, PropertyType.BE, property, type); + + public static void register(Class condition, String property, String type) { + register(condition, PropertyType.BE, property, type); } - + /** - * @param c the class to register + * @param condition the class to register * @param propertyType the property type, see {@link PropertyType} * @param property the property name, for example fly in players can fly * @param type must be plural, for example players in players can fly */ - public static void register(final Class c, final PropertyType propertyType, final String property, final String type) { - if (type.contains("%")) { + + public static void register(Class condition, PropertyType propertyType, String property, String type) { + if (type.contains("%")) throw new SkriptAPIException("The type argument must not contain any '%'s"); - } + switch (propertyType) { case BE: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% (is|are) " + property, "%" + type + "% (isn't|is not|aren't|are not) " + property); break; case CAN: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% can " + property, "%" + type + "% (can't|cannot|can not) " + property); break; case HAVE: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% (has|have) " + property, "%" + type + "% (doesn't|does not|do not|don't) have " + property); break; + case WILL: + Skript.registerCondition(condition, + "%" + type + "% will " + property, + "%" + type + "% (will (not|neither)|won't) " + property); + break; default: assert false; } } - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - expr = (Expression) exprs[0]; + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + expr = (Expression) expressions[0]; + setNegated(matchedPattern == 1); return true; } - + @Override - public final boolean check(final Event e) { - return expr.check(e, this, isNegated()); + public final boolean check(Event event) { + return expr.check(event, this, isNegated()); } - + @Override - public abstract boolean check(T t); + public abstract boolean check(T value); protected abstract String getPropertyName(); - + protected PropertyType getPropertyType() { return PropertyType.BE; } - + /** * Sets the expression this condition checks a property of. No reference to the expression should be kept. * - * @param expr + * @param expr The expression property of this property condition. */ - protected final void setExpr(final Expression expr) { + protected final void setExpr(Expression expr) { this.expr = expr; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return toString(this, getPropertyType(), e, debug, expr, getPropertyName()); + public String toString(@Nullable Event event, boolean debug) { + return toString(this, getPropertyType(), event, debug, expr, getPropertyName()); } - - public static String toString(Condition condition, PropertyType propertyType, @Nullable Event e, + + public static String toString(Condition condition, PropertyType propertyType, @Nullable Event event, boolean debug, Expression expr, String property) { switch (propertyType) { case BE: - return expr.toString(e, debug) + (expr.isSingle() ? " is " : " are ") + (condition.isNegated() ? "not " : "") + property; + return expr.toString(event, debug) + (expr.isSingle() ? " is " : " are ") + (condition.isNegated() ? "not " : "") + property; case CAN: - return expr.toString(e, debug) + (condition.isNegated() ? " can't " : " can ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " can't " : " can ") + property; case HAVE: if (expr.isSingle()) - return expr.toString(e, debug) + (condition.isNegated() ? " doesn't have " : " has ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " doesn't have " : " has ") + property; else - return expr.toString(e, debug) + (condition.isNegated() ? " don't have " : " have ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " don't have " : " have ") + property; + case WILL: + return expr.toString(event, debug) + (condition.isNegated() ? " won't " : " will ") + "be " + property; default: assert false; throw new AssertionError(); diff --git a/src/main/java/ch/njol/skript/config/Node.java b/src/main/java/ch/njol/skript/config/Node.java index 170fd38cf42..afb3ea3d038 100644 --- a/src/main/java/ch/njol/skript/config/Node.java +++ b/src/main/java/ch/njol/skript/config/Node.java @@ -128,6 +128,8 @@ public void move(final SectionNode newParent) { * @return A pair (value, comment). */ public static NonNullPair splitLine(final String line) { + if (line.trim().startsWith("#")) + return new NonNullPair<>("", line.substring(line.indexOf('#'))); final Matcher m = linePattern.matcher(line); boolean matches = false; try { diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index c7b9ebebbb1..9797e1b2188 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.doc; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.conditions.CondCompare; @@ -47,6 +35,17 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.IteratorIterable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * TODO list special expressions for events and event values @@ -305,28 +304,29 @@ public String run(final Matcher m) { } private static void insertSyntaxElement(final PrintWriter pw, final SyntaxElementInfo info, final String type) { - if (info.c.getAnnotation(NoDoc.class) != null) + Class elementClass = info.getElementClass(); + if (elementClass.getAnnotation(NoDoc.class) != null) return; - if (info.c.getAnnotation(Name.class) == null || info.c.getAnnotation(Description.class) == null || info.c.getAnnotation(Examples.class) == null || info.c.getAnnotation(Since.class) == null) { - Skript.warning("" + info.c.getSimpleName() + " is missing information"); + if (elementClass.getAnnotation(Name.class) == null || elementClass.getAnnotation(Description.class) == null || elementClass.getAnnotation(Examples.class) == null || elementClass.getAnnotation(Since.class) == null) { + Skript.warning("" + elementClass.getSimpleName() + " is missing information"); return; } - final String desc = validateHTML(StringUtils.join(info.c.getAnnotation(Description.class).value(), "
"), type + "s"); - final String since = validateHTML(info.c.getAnnotation(Since.class).value(), type + "s"); + final String desc = validateHTML(StringUtils.join(elementClass.getAnnotation(Description.class).value(), "
"), type + "s"); + final String since = validateHTML(elementClass.getAnnotation(Since.class).value(), type + "s"); if (desc == null || since == null) { - Skript.warning("" + info.c.getSimpleName() + "'s description or 'since' is invalid"); + Skript.warning("" + elementClass.getSimpleName() + "'s description or 'since' is invalid"); return; } - final String patterns = cleanPatterns(StringUtils.join(info.patterns, "\n", 0, info.c == CondCompare.class ? 8 : info.patterns.length)); + final String patterns = cleanPatterns(StringUtils.join(info.patterns, "\n", 0, elementClass == CondCompare.class ? 8 : info.patterns.length)); insertOnDuplicateKeyUpdate(pw, "syntax_elements", "id, name, type, patterns, description, examples, since", "patterns = TRIM(LEADING '\n' FROM CONCAT(patterns, '\n', '" + escapeSQL(patterns) + "'))", - escapeHTML("" + info.c.getSimpleName()), - escapeHTML(info.c.getAnnotation(Name.class).value()), + escapeHTML("" + elementClass.getSimpleName()), + escapeHTML(elementClass.getAnnotation(Name.class).value()), type, patterns, desc, - escapeHTML(StringUtils.join(info.c.getAnnotation(Examples.class).value(), "\n")), + escapeHTML(StringUtils.join(elementClass.getAnnotation(Examples.class).value(), "\n")), since); } @@ -334,7 +334,7 @@ private static void insertEvent(final PrintWriter pw, final SkriptEventInfo i if (info.getDescription() == SkriptEventInfo.NO_DOC) return; if (info.getDescription() == null || info.getExamples() == null || info.getSince() == null) { - Skript.warning("" + info.getName() + " (" + info.c.getSimpleName() + ") is missing information"); + Skript.warning("" + info.getName() + " (" + info.getElementClass().getSimpleName() + ") is missing information"); return; } for (final SkriptEventInfo i : Skript.getEvents()) { @@ -346,7 +346,7 @@ private static void insertEvent(final PrintWriter pw, final SkriptEventInfo i final String desc = validateHTML(StringUtils.join(info.getDescription(), "
"), "events"); final String since = validateHTML(info.getSince(), "events"); if (desc == null || since == null) { - Skript.warning("description or 'since' of " + info.getName() + " (" + info.c.getSimpleName() + ") is invalid"); + Skript.warning("description or 'since' of " + info.getName() + " (" + info.getElementClass().getSimpleName() + ") is invalid"); return; } final String patterns = cleanPatterns(info.getName().startsWith("On ") ? "[on] " + StringUtils.join(info.patterns, "\n[on] ") : StringUtils.join(info.patterns, "\n")); diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 9a4adca6d7b..e882057cebd 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -86,19 +86,19 @@ public HTMLGenerator(File templateDir, File outputDir) { throw new NullPointerException(); } - if (o1.c.getAnnotation(NoDoc.class) != null) { - if (o2.c.getAnnotation(NoDoc.class) != null) + if (o1.getElementClass().getAnnotation(NoDoc.class) != null) { + if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return 0; return 1; - } else if (o2.c.getAnnotation(NoDoc.class) != null) + } else if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return -1; - Name name1 = o1.c.getAnnotation(Name.class); - Name name2 = o2.c.getAnnotation(Name.class); + Name name1 = o1.getElementClass().getAnnotation(Name.class); + Name name2 = o2.getElementClass().getAnnotation(Name.class); if (name1 == null) - throw new SkriptAPIException("Name annotation expected: " + o1.c); + throw new SkriptAPIException("Name annotation expected: " + o1.getElementClass()); if (name2 == null) - throw new SkriptAPIException("Name annotation expected: " + o2.c); + throw new SkriptAPIException("Name annotation expected: " + o2.getElementClass()); return name1.value().compareTo(name2.value()); }; @@ -115,8 +115,8 @@ private static Iterator> sortedAnnotatedItera while (it.hasNext()) { SyntaxElementInfo item = it.next(); // Filter unnamed expressions (mostly caused by addons) to avoid throwing exceptions and stop the generation process - if (item.c.getAnnotation(Name.class) == null && item.c.getAnnotation(NoDoc.class) == null) { - Skript.warning("Skipped generating '" + item.c + "' class due to missing Name annotation"); + if (item.getElementClass().getAnnotation(Name.class) == null && item.getElementClass().getAnnotation(NoDoc.class) == null) { + Skript.warning("Skipped generating '" + item.getElementClass() + "' class due to missing Name annotation"); continue; } list.add(item); @@ -141,9 +141,9 @@ public int compare(@Nullable SkriptEventInfo o1, @Nullable SkriptEventInfo throw new NullPointerException(); } - if (o1.c.getAnnotation(NoDoc.class) != null) + if (o1.getElementClass().getAnnotation(NoDoc.class) != null) return 1; - else if (o2.c.getAnnotation(NoDoc.class) != null) + else if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return -1; return o1.name.compareTo(o2.name); @@ -285,7 +285,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { StructureInfo info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; String desc = generateAnnotated(descTemp, info, generated.toString(), "Structure"); generated.append(desc); @@ -296,7 +296,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator> it = sortedAnnotatedIterator((Iterator) Skript.getExpressions()); it.hasNext(); ) { ExpressionInfo info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; String desc = generateAnnotated(descTemp, info, generated.toString(), "Expression"); generated.append(desc); @@ -306,7 +306,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator> it = sortedAnnotatedIterator(Skript.getEffects().iterator()); it.hasNext(); ) { SyntaxElementInfo info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "Effect")); } @@ -314,8 +314,8 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { SyntaxElementInfo info = it.next(); assert info != null; - if (EffectSection.class.isAssignableFrom(info.c)) { - if (info.c.getAnnotation(NoDoc.class) != null) + if (EffectSection.class.isAssignableFrom(info.getElementClass())) { + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "EffectSection")); } @@ -325,7 +325,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator> it = sortedAnnotatedIterator(Skript.getConditions().iterator()); it.hasNext(); ) { SyntaxElementInfo info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "Condition")); } @@ -334,9 +334,9 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { SyntaxElementInfo info = it.next(); assert info != null; - boolean isEffectSection = EffectSection.class.isAssignableFrom(info.c); + boolean isEffectSection = EffectSection.class.isAssignableFrom(info.getElementClass()); // exclude sections that are EffectSection from isDocsPage, they are added by the effects block above - if ((isEffectSection && isDocsPage) || info.c.getAnnotation(NoDoc.class) != null) + if ((isEffectSection && isDocsPage) || info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), (isEffectSection ? "Effect" : "") + "Section")); } @@ -346,7 +346,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { events.sort(eventComparator); for (SkriptEventInfo info : events) { assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateEvent(descTemp, info, generated.toString())); } @@ -444,7 +444,7 @@ private static String handleIf(String desc, String start, boolean value) { * @return Generated HTML entry. */ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nullable String page, String type) { - Class c = info.c; + Class c = info.getElementClass(); String desc; // Name @@ -472,7 +472,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu // Documentation ID DocumentationId docId = c.getAnnotation(DocumentationId.class); - String ID = docId != null ? (docId != null ? docId.value() : null) : info.c.getSimpleName(); + String ID = docId != null ? (docId != null ? docId.value() : null) : c.getSimpleName(); // Fix duplicated IDs if (page != null) { if (page.contains("href=\"#" + ID + "\"")) { @@ -569,7 +569,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu } private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable String page) { - Class c = info.c; + Class c = info.getElementClass(); String desc; // Name @@ -662,8 +662,7 @@ private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable StringBuilder patterns = new StringBuilder(); for (String line : getDefaultIfNullOrEmpty(info.patterns, "Missing patterns.")) { assert line != null; - line = cleanPatterns(line); - line = line.replace(SkriptEventInfo.EVENT_PRIORITY_SYNTAX, ""); // replace priority syntax in event syntaxes + line = "[on] " + cleanPatterns(line); String parsed = pattern.replace("${element.pattern}", line); patterns.append(parsed); } diff --git a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java index 484654a14f6..2b6c4e6ff63 100644 --- a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java +++ b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java @@ -38,7 +38,7 @@ @Description("Applies bone meal to a crop, sapling, or composter") @Examples("apply 3 bone meal to event-block") @RequiredPlugins("MC 1.16.2+") -@Since("INSERT VERSION") +@Since("2.8.0") public class EffApplyBoneMeal extends Effect { static { @@ -71,7 +71,7 @@ protected void execute(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - return "apply " + amount != null ? amount.toString(event, debug) + " " : "" + "bone meal to " + blocks.toString(event, debug); + return "apply " + (amount != null ? amount.toString(event, debug) + " " : "" + "bone meal to " + blocks.toString(event, debug)); } } diff --git a/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java new file mode 100644 index 00000000000..20251dbb82b --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java @@ -0,0 +1,77 @@ +/** + * 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.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.entity.LivingEntity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Cancel Active Item") +@Description({ + "Interrupts the action entities may be trying to complete.", + "For example, interrupting eating, or drawing back a bow." +}) +@Examples({ + "on damage of player:", + "\tif the victim's active tool is a bow:", + "\t\tinterrupt the usage of the player's active item" +}) +@Since("2.8.0") +@RequiredPlugins("Paper 1.16+") +public class EffCancelItemUse extends Effect { + + static { + if (Skript.methodExists(LivingEntity.class, "clearActiveItem")) + Skript.registerEffect(EffCancelItemUse.class, + "(cancel|interrupt) [the] us[ag]e of %livingentities%'[s] [active|current] item" + ); + } + + private Expression entities; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (LivingEntity entity : entities.getArray(event)) { + entity.clearActiveItem(); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "cancel the usage of " + entities.toString(event, debug) + "'s active item"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 6b16e36071f..9c355191ab2 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -21,7 +21,12 @@ import java.util.Arrays; import java.util.logging.Level; -import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.expressions.ExprParse; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.Variable; import org.skriptlang.skript.lang.script.ScriptWarning; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -35,11 +40,7 @@ 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; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.ParseLogHandler; @@ -254,7 +255,18 @@ else if (mode == ChangeMode.SET) Skript.error("only one " + Classes.getSuperClassInfo(x).getName() + " can be " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " " + changed + ", not more", ErrorQuality.SEMANTIC_ERROR); return false; } - + + if (changed instanceof Variable && !changed.isSingle() && mode == ChangeMode.SET) { + if (ch instanceof ExprParse) { + ((ExprParse) ch).flatten = false; + } else if (ch instanceof ExpressionList) { + for (Expression expression : ((ExpressionList) ch).getExpressions()) { + if (expression instanceof ExprParse) + ((ExprParse) expression).flatten = false; + } + } + } + if (changed instanceof Variable && !((Variable) changed).isLocal() && (mode == ChangeMode.SET || ((Variable) changed).isList() && mode == ChangeMode.ADD)) { final ClassInfo ci = Classes.getSuperClassInfo(ch.getReturnType()); if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null && !SkriptConfig.disableObjectCannotBeSavedWarnings.value()) { diff --git a/src/main/java/ch/njol/skript/effects/EffCommand.java b/src/main/java/ch/njol/skript/effects/EffCommand.java index 3a26fd82dd7..5977dbcc61a 100644 --- a/src/main/java/ch/njol/skript/effects/EffCommand.java +++ b/src/main/java/ch/njol/skript/effects/EffCommand.java @@ -48,16 +48,16 @@ "execute console command \"/say Hello everyone!\"", "execute player bungeecord command \"/alert &6Testing Announcement!\"" }) -@Since("1.0, INSERT VERSION (bungeecord command)") +@Since("1.0, 2.8.0 (bungeecord command)") public class EffCommand extends Effect { public static final String MESSAGE_CHANNEL = "Message"; static { Skript.registerEffect(EffCommand.class, - "[execute] [the] [bungee:bungee[cord]] command %strings% [by %-commandsenders%]", - "[execute] [the] %commandsenders% [bungee:bungee[cord]] command %strings%", - "(let|make) %commandsenders% execute [[the] [bungee:bungee[cord]] command] %strings%"); + "[execute] [the] [bungee:bungee[cord]] command[s] %strings% [by %-commandsenders%]", + "[execute] [the] %commandsenders% [bungee:bungee[cord]] command[s] %strings%", + "(let|make) %commandsenders% execute [[the] [bungee:bungee[cord]] command[s]] %strings%"); } @Nullable diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 4b4c4b3682c..e882b202986 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -55,7 +55,7 @@ "\t\tcontinue # only print when counter is 1, 2, 3, 5 or 10", "\tbroadcast \"Game starting in %{_counter}% second(s)\"", }) -@Since("2.2-dev37, 2.7 (while loops), INSERT VERSION (outer loops)") +@Since("2.2-dev37, 2.7 (while loops), 2.8.0 (outer loops)") public class EffContinue extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffCopy.java b/src/main/java/ch/njol/skript/effects/EffCopy.java new file mode 100644 index 00000000000..e949f3bd832 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffCopy.java @@ -0,0 +1,166 @@ +/** + * 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.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.*; + +@Name("Copy Into Variable") +@Description({ + "Copies objects into a variable. When copying a list over to another list, the source list and its sublists are also copied over.", + "Note: Copying a value into a variable/list will overwrite the existing data." +}) +@Examples({ + "set {_foo::bar} to 1", + "set {_foo::sublist::foobar} to \"hey\"", + "copy {_foo::*} to {_copy::*}", + "broadcast indices of {_copy::*} # bar, sublist", + "broadcast {_copy::bar} # 1", + "broadcast {_copy::sublist::foobar} # \"hey!\"" +}) +@Since("2.8.0") +@Keywords({"clone", "variable", "list"}) +public class EffCopy extends Effect { + + static { + Skript.registerEffect(EffCopy.class, "copy %~objects% [in]to %~objects%"); + } + + private Expression source; + private Expression rawDestination; + private List> destinations; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + source = exprs[0]; + rawDestination = exprs[1]; + if (exprs[1] instanceof Variable) { + destinations = Collections.singletonList((Variable) exprs[1]); + } else if (exprs[1] instanceof ExpressionList) { + destinations = unwrapExpressionList((ExpressionList) exprs[1]); + } + if (destinations == null) { + Skript.error("You can only copy objects into variables"); + return false; + } + for (Variable destination : destinations) { + if (!source.isSingle() && destination.isSingle()) { + Skript.error("Cannot copy multiple objects into a single variable"); + return false; + } + } + return true; + } + + @Override + @SuppressWarnings("unchecked") + protected void execute(Event event) { + if (!(source instanceof Variable) || source.isSingle()) { + ChangeMode mode = ChangeMode.SET; + Object[] clone = (Object[]) Classes.clone(source.getArray(event)); + if (clone.length == 0) + mode = ChangeMode.DELETE; + for (Variable dest : destinations) + dest.change(event, clone, mode); + return; + } + + Map source = copyMap((Map) ((Variable) this.source).getRaw(event)); + + // If we're copying {_foo::*} we don't want to also copy {_foo} + if (source != null) + source.remove(null); + + for (Variable destination : destinations) { + destination.change(event, null, ChangeMode.DELETE); + if (source == null) + continue; + + String target = destination.getName().getSingle(event); + target = target.substring(0, target.length() - (Variable.SEPARATOR + "*").length()); + set(event, target, source, destination.isLocal()); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "copy " + source.toString(event, debug) + " into " + rawDestination.toString(event, debug); + } + + @SuppressWarnings("unchecked") + @Nullable + private static Map copyMap(@Nullable Map map) { + if (map == null) + return null; + Map copy = new HashMap<>(map.size()); + map.forEach((key, value) -> { + if (value instanceof Map) { + copy.put(key, copyMap((Map) value)); + return; + } + copy.put(key, Classes.clone(value)); + }); + return copy; + } + + @SuppressWarnings("unchecked") + private static void set(Event event, String targetName, Map source, boolean local) { + source.forEach((key, value) -> { + String node = targetName + (key == null ? "" : Variable.SEPARATOR + key); + if (value instanceof Map) { + set(event, node, (Map) value, local); + return; + } + Variables.setVariable(node, value, event, local); + }); + } + + private static List> unwrapExpressionList(ExpressionList expressionList) { + Expression[] expressions = expressionList.getExpressions(); + List> destinations = new ArrayList<>(); + for (Expression expression : expressions) { + if (expression instanceof Variable) { + destinations.add((Variable) expression); + continue; + } + if (!(expression instanceof ExpressionList)) + return null; + destinations.addAll(unwrapExpressionList((ExpressionList) expression)); + } + return destinations; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffExit.java b/src/main/java/ch/njol/skript/effects/EffExit.java index eecbb7efec1..b5e2be7e04a 100644 --- a/src/main/java/ch/njol/skript/effects/EffExit.java +++ b/src/main/java/ch/njol/skript/effects/EffExit.java @@ -25,6 +25,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SectionExitHandler; import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; @@ -122,8 +123,8 @@ protected TriggerItem walk(Event event) { assert false : this; return null; } - if (node instanceof LoopSection) - ((LoopSection) node).exit(event); + if (node instanceof SectionExitHandler) + ((SectionExitHandler) node).exit(event); if (type == EVERYTHING || type == CONDITIONALS && node instanceof SecConditional || type == LOOPS && (node instanceof LoopSection)) i--; diff --git a/src/main/java/ch/njol/skript/effects/EffExplosion.java b/src/main/java/ch/njol/skript/effects/EffExplosion.java index 4f0de85dd86..192119a54d6 100644 --- a/src/main/java/ch/njol/skript/effects/EffExplosion.java +++ b/src/main/java/ch/njol/skript/effects/EffExplosion.java @@ -37,7 +37,7 @@ * @author Peter Güttinger */ @Name("Explosion") -@Description({"Creates an explosion of a given force. The Minecraft Wiki has an article on explosions " + +@Description({"Creates an explosion of a given force. The Minecraft Wiki has an article on explosions " + "which lists the explosion forces of TNT, creepers, etc.", "Hint: use a force of 0 to create a fake explosion that does no damage whatsoever, or use the explosion effect introduced in Skript 2.0.", "Starting with Bukkit 1.4.5 and Skript 2.0 you can use safe explosions which will damage entities but won't destroy any blocks."}) diff --git a/src/main/java/ch/njol/skript/effects/EffGlowingText.java b/src/main/java/ch/njol/skript/effects/EffGlowingText.java new file mode 100644 index 00000000000..7f5b3161396 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffGlowingText.java @@ -0,0 +1,98 @@ +/** + * 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.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.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Make Sign Glow") +@Description("Makes a sign (either a block or item) have glowing text or normal text") +@Examples("make target block of player have glowing text") +@Since("2.8.0") +public class EffGlowingText extends Effect { + + static { + if (Skript.methodExists(Sign.class, "setGlowingText", boolean.class)) { + Skript.registerEffect(EffGlowingText.class, + "make %blocks/itemtypes% have glowing text", + "make %blocks/itemtypes% have (normal|non[-| ]glowing) text" + ); + } + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression objects; + + private boolean glowing; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + glowing = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (Object obj : objects.getArray(event)) { + if (obj instanceof Block) { + BlockState state = ((Block) obj).getState(); + if (state instanceof Sign) { + ((Sign) state).setGlowingText(glowing); + state.update(); + } + } else if (obj instanceof ItemType) { + ItemType item = (ItemType) obj; + ItemMeta meta = item.getItemMeta(); + if (!(meta instanceof BlockStateMeta)) + return; + BlockStateMeta blockMeta = (BlockStateMeta) meta; + BlockState state = blockMeta.getBlockState(); + if (!(state instanceof Sign)) + return; + ((Sign) state).setGlowingText(glowing); + state.update(); + blockMeta.setBlockState(state); + item.setItemMeta(meta); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + objects.toString(event, debug) + " have " + (glowing ? "glowing text" : "normal text"); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffHandedness.java b/src/main/java/ch/njol/skript/effects/EffHandedness.java index ddb8a8d0787..89d5469e1a1 100644 --- a/src/main/java/ch/njol/skript/effects/EffHandedness.java +++ b/src/main/java/ch/njol/skript/effects/EffHandedness.java @@ -41,7 +41,7 @@ "", "make all zombies in radius 10 of player right handed" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.17.1+") public class EffHandedness extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffInvulnerability.java b/src/main/java/ch/njol/skript/effects/EffInvulnerability.java index 0e9d32d415b..33acd611202 100644 --- a/src/main/java/ch/njol/skript/effects/EffInvulnerability.java +++ b/src/main/java/ch/njol/skript/effects/EffInvulnerability.java @@ -40,8 +40,8 @@ public class EffInvulnerability extends Effect { static { Skript.registerEffect(EffInvulnerability.class, - "make %entities% invulnerable", - "make %entities% (not invulnerable|vulnerable)"); + "make %entities% (invulnerable|invincible)", + "make %entities% (not (invulnerable|invincible)|vulnerable|vincible)"); } @SuppressWarnings("null") @@ -57,15 +57,15 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - for (Entity entity : entities.getArray(e)) { + protected void execute(Event event) { + for (Entity entity : entities.getArray(event)) { entity.setInvulnerable(invulnerable); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "make " + entities.toString(e, debug) + (invulnerable ? " invulnerable" : " not invulnerable"); + public String toString(@Nullable Event event, boolean debug) { + return "make " + entities.toString(event, debug) + (invulnerable ? " invulnerable" : " not invulnerable"); } } diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index e418c8c0523..78d2c86d089 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -47,7 +47,7 @@ "\tapply potion of strength of tier {strength::%uuid of player%} to the player for 999 days # Before 1.19.4", "", "apply potion effects of player's tool to player", - "apply haste potion of tier 3 without any particles to the player whilst hiding the potion icon # Hide potions" + "apply haste potion of tier 3 without any particles whilst hiding the potion icon to the player # Hide potions" }) @Since( "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 13b563fefee..d0b4d220514 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -26,7 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.LoopSection; +import ch.njol.skript.lang.SectionExitHandler; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; @@ -48,7 +48,7 @@ "function divide(i: number) returns number:", "\treturn {_i} / 2" }) -@Since("2.2, INSERT VERSION (returns aliases)") +@Since("2.2, 2.8.0 (returns aliases)") public class EffReturn extends Effect { static { @@ -117,8 +117,8 @@ protected TriggerItem walk(Event event) { TriggerSection parent = getParent(); while (parent != null) { - if (parent instanceof LoopSection) - ((LoopSection) parent).exit(event); + if (parent instanceof SectionExitHandler) + ((SectionExitHandler) parent).exit(event); parent = parent.getParent(); } diff --git a/src/main/java/ch/njol/skript/effects/EffRing.java b/src/main/java/ch/njol/skript/effects/EffRing.java new file mode 100644 index 00000000000..8b66e1ce488 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffRing.java @@ -0,0 +1,110 @@ +/** + * 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.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.skript.util.Direction; +import ch.njol.util.Kleenean; +import org.bukkit.block.Bell; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Ring Bell") +@Description({ + "Causes a bell to ring.", + "Optionally, the entity that rang the bell and the direction the bell should ring can be specified.", + "A bell can only ring in two directions, and the direction is determined by which way the bell is facing.", + "By default, the bell will ring in the direction it is facing.", +}) +@Examples({"make player ring target-block"}) +@RequiredPlugins("Spigot 1.19.4+") +@Since("INSERT VERSION") +public class EffRing extends Effect { + + static { + if (Skript.classExists("org.bukkit.block.Bell") && Skript.methodExists(Bell.class, "ring", Entity.class, BlockFace.class)) + Skript.registerEffect(EffRing.class, + "ring %blocks% [from [the]] [%-direction%]", + "(make|let) %entity% ring %blocks% [from [the]] [%-direction%]" + ); + } + + @Nullable + private Expression entity; + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression blocks; + + @Nullable + private Expression direction; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entity = matchedPattern == 0 ? null : (Expression) exprs[0]; + blocks = (Expression) exprs[matchedPattern]; + direction = (Expression) exprs[matchedPattern + 1]; + return true; + } + + @Nullable + private BlockFace getBlockFace(Event event) { + if (this.direction == null) + return null; + + Direction direction = this.direction.getSingle(event); + if (direction == null) + return null; + + return Direction.getFacing(direction.getDirection(), true); + } + + @Override + protected void execute(Event event) { + BlockFace blockFace = getBlockFace(event); + Entity actualEntity = entity == null ? null : entity.getSingle(event); + + for (Block block : blocks.getArray(event)) { + BlockState state = block.getState(false); + if (state instanceof Bell) { + Bell bell = (Bell) state; + bell.ring(actualEntity, blockFace); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (entity != null ? "make " + entity.toString(event, debug) + " " : "") + + "ring " + blocks.toString(event, debug) + " from " + (direction != null ? direction.toString(event, debug) : ""); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffScriptFile.java b/src/main/java/ch/njol/skript/effects/EffScriptFile.java index 0a38111c5ce..ed15f625d2f 100644 --- a/src/main/java/ch/njol/skript/effects/EffScriptFile.java +++ b/src/main/java/ch/njol/skript/effects/EffScriptFile.java @@ -37,6 +37,7 @@ import java.io.File; import java.io.IOException; +import java.util.Set; @Name("Enable/Disable/Reload Script File") @Description("Enables, disables, or reloads a script file.") @@ -102,10 +103,8 @@ protected void execute(Event e) { case RELOAD: { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + + this.unloadScripts(scriptFile); ScriptLoader.loadScripts(scriptFile, OpenCloseable.EMPTY); break; @@ -114,9 +113,7 @@ protected void execute(Event e) { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + this.unloadScripts(scriptFile); try { FileUtils.move( @@ -135,6 +132,19 @@ protected void execute(Event e) { assert false; } } + + private void unloadScripts(File file) { + if (file.isDirectory()) { + Set