diff --git a/.github/workflows/archive-docs.yml b/.github/workflows/archive-docs.yml new file mode 100644 index 00000000000..e183f748d78 --- /dev/null +++ b/.github/workflows/archive-docs.yml @@ -0,0 +1,45 @@ +name: Archive documentation + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + archive-docs: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + needs: release-docs + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + run: | + echo "BRANCH_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/archives/${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT + echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT + - name: Checkout Skript + uses: actions/checkout@v4 + with: + submodules: recursive + path: skript + - name: Setup documentation environment + uses: ./skript/.github/workflows/docs/setup-docs + with: + docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + - name: Generate documentation + uses: ./skript/.github/workflows/docs/generate-docs + with: + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} + is_release: true + generate_javadocs: true + - name: Push archive documentation + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Archive Docs Bot + git_email: archivedocs@skriptlang.org + git_commit_message: "Update ${{ steps.configuration.outputs.BRANCH_NAME }} archive docs" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index e033996868e..2470f852450 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -69,7 +69,7 @@ runs: cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then - ./gradlew genReleaseDocs releaseJavadoc + ./gradlew genReleaseDocs javadoc elif [[ "${GENERATE_JAVADOCS}" == "true" ]]; then ./gradlew genNightlyDocs javadoc else @@ -77,7 +77,7 @@ runs: fi if [ -d "${DOCS_OUTPUT_DIR}" ]; then - if [[ "${GENERATE_JAVADOCS}" == "true" ]]; then + if [[ "${GENERATE_JAVADOCS}" == "true" ]] || [[ "${IS_RELEASE}" == "true" ]] ; then mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" fi diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index f6f7ebc8a27..9cfe198ba93 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -3,6 +3,7 @@ name: Release documentation on: release: types: [published] + workflow_dispatch: jobs: release-docs: @@ -33,6 +34,7 @@ jobs: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true + generate_javadocs: true cleanup_pattern: "!(nightly|archives|templates)" - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs @@ -41,40 +43,3 @@ jobs: git_name: Release Docs Bot git_email: releasedocs@skriptlang.org git_commit_message: "Update release docs to ${{ steps.configuration.outputs.BRANCH_NAME }}" - - archive-docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - needs: release-docs - runs-on: ubuntu-latest - steps: - - name: Configure workflow - id: configuration - run: | - echo "BRANCH_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/archives/${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT - echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT - - name: Checkout Skript - uses: actions/checkout@v4 - with: - submodules: recursive - path: skript - - name: Setup documentation environment - uses: ./skript/.github/workflows/docs/setup-docs - with: - docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - - name: Generate documentation - 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 - uses: ./skript/.github/workflows/docs/push-docs - with: - docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} - git_name: Archive Docs Bot - git_email: archivedocs@skriptlang.org - git_commit_message: "Update ${{ steps.configuration.outputs.BRANCH_NAME }} archive docs" diff --git a/build.gradle b/build.gradle index 4cc665864db..161d3dd3daf 100644 --- a/build.gradle +++ b/build.gradle @@ -140,15 +140,6 @@ publishing { } } -task releaseJavadoc(type: Javadoc) { - title = project.name + ' ' + project.property('version') - source = sourceSets.main.allJava - classpath = configurations.compileClasspath - options.encoding = 'UTF-8' - // currently our javadoc has a lot of errors, so we need to suppress the linter - options.addStringOption('Xdoclint:none', '-quiet') -} - // Task to check that test scripts are named correctly tasks.register('testNaming') { doLast { @@ -188,7 +179,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi if (junit) { artifact += 'Skript-JUnit.jar' } else if (releaseDocs) { - artifact += 'Skript-github.jar' + artifact += 'Skript-' + version + '.jar' } else { artifact += 'Skript-nightly.jar' } @@ -367,7 +358,7 @@ task nightlyResources(type: ProcessResources) { 'today' : '' + LocalTime.now(), 'release-flavor' : 'skriptlang-nightly', // SkriptLang build, automatically done by CI 'release-channel' : 'prerelease', // No update checking, but these are VERY unstable - 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No autoupdates for now + 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No auto updates for now 'release-source' : '', 'release-download': 'null' ] @@ -389,8 +380,8 @@ task nightlyRelease(type: ShadowJar) { } javadoc { - dependsOn nightlyResources - + mustRunAfter(tasks.withType(ProcessResources)) + title = 'Skript ' + project.property('version') source = sourceSets.main.allJava exclude("ch/njol/skript/conditions/**") @@ -409,4 +400,3 @@ javadoc { // currently our javadoc has a lot of errors, so we need to suppress the linter options.addStringOption('Xdoclint:none', '-quiet') } - diff --git a/gradle.properties b/gradle.properties index 0b5b23f30d5..f78ae1766eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.6 +version=2.8.7 jarName=Skript.jar testEnv=java21/paper-1.20.6 testEnvJavaVersion=21 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 25000a2390d..ba1a419aad0 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -38,6 +38,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.Registry; import org.bukkit.SoundCategory; import org.bukkit.World; import org.bukkit.World.Environment; @@ -77,7 +78,6 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.util.CachedServerIcon; import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; @@ -90,6 +90,7 @@ import ch.njol.skript.classes.EnumClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.entity.EntityData; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; @@ -104,6 +105,7 @@ import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; import io.papermc.paper.world.MoonPhase; +import org.jetbrains.annotations.Nullable; /** * @author Peter Güttinger @@ -976,8 +978,14 @@ public String toVariableNameString(final ItemStack i) { .name(ClassInfo.NO_DOC) .since("2.0") .changer(DefaultChangers.itemChanger)); - - Classes.registerClass(new EnumClassInfo<>(Biome.class, "biome", "biomes") + + ClassInfo biomeClassInfo; + if (Skript.classExists("org.bukkit.Registry") && Skript.fieldExists(Registry.class, "BIOME")) { + biomeClassInfo = new RegistryClassInfo<>(Biome.class, Registry.BIOME, "biome", "biomes"); + } else { + biomeClassInfo = new EnumClassInfo<>(Biome.class, "biome", "biomes"); + } + Classes.registerClass(biomeClassInfo .user("biomes?") .name("Biome") .description("All possible biomes Minecraft uses to generate a world.") 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 e1001a0afa4..6131def1eec 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -18,9 +18,6 @@ */ package ch.njol.skript.classes.data; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -664,34 +661,17 @@ public LivingEntity[] get(AreaEffectCloudApplyEvent event) { } }, EventValues.TIME_NOW); EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, PotionEffectType.class, new Getter() { - @Nullable - private final MethodHandle BASE_POTION_DATA_HANDLE; - - { - MethodHandle basePotionDataHandle = null; - if (Skript.methodExists(AreaEffectCloud.class, "getBasePotionData")) { - try { - basePotionDataHandle = MethodHandles.lookup().findVirtual(AreaEffectCloud.class, "getBasePotionData", MethodType.methodType(PotionData.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - Skript.exception(e, "Failed to load legacy potion data support. Potions may not work as expected."); - } - } - BASE_POTION_DATA_HANDLE = basePotionDataHandle; - } - + private final boolean HAS_POTION_TYPE_METHOD = Skript.methodExists(AreaEffectCloud.class, "getBasePotionType"); @Override @Nullable public PotionEffectType get(AreaEffectCloudApplyEvent e) { - if (BASE_POTION_DATA_HANDLE != null) { - try { - return ((PotionData) BASE_POTION_DATA_HANDLE.invoke(e.getEntity())).getType().getEffectType(); - } catch (Throwable ex) { - throw Skript.exception(ex, "An error occurred while trying to invoke legacy area effect cloud potion effect support."); - } - } else { + // TODO needs to be reworked to support multiple values (there can be multiple potion effects) + if (HAS_POTION_TYPE_METHOD) { PotionType base = e.getEntity().getBasePotionType(); - if (base != null) // TODO this is deprecated... this should become a multi-value event value + if (base != null) return base.getEffectType(); + } else { + return e.getEntity().getBasePotionData().getType().getEffectType(); } return null; } 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 39dc6447fa5..6f92502cd2a 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -661,6 +661,7 @@ public String toVariableNameString(final Experience xp) { .usage(VisualEffects.getAllNames()) .since("2.1") .user("(visual|particle) effects?") + .after("itemtype") .parser(new Parser() { @Override @Nullable diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java new file mode 100644 index 00000000000..ff82f966af8 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java @@ -0,0 +1,53 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.classes.registry; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.DefaultExpression; +import org.bukkit.Keyed; +import org.bukkit.Registry; + +/** + * This class can be used for easily creating ClassInfos for {@link Registry}s. + * It registers a language node with usage, a serializer, default expression, and a parser. + * + * @param The Registry class. + */ +public class RegistryClassInfo extends ClassInfo { + + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode) { + this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass)); + } + + /** + * @param registry The registry + * @param codeName The name used in patterns + */ + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression) { + super(registryClass, codeName); + RegistryParser registryParser = new RegistryParser<>(registry, languageNode); + usage(registryParser.getAllNames()) + .supplier(registry::iterator) + .serializer(new RegistrySerializer(registry)) + .defaultExpression(defaultExpression) + .parser(registryParser); + } + +} diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java new file mode 100644 index 00000000000..51cb4637434 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryParser.java @@ -0,0 +1,155 @@ +/** + * 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.registry; + +import ch.njol.skript.classes.Parser; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.Noun; +import ch.njol.util.NonNullPair; +import ch.njol.util.StringUtils; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * A parser based on a {@link Registry} used to parse data from a string or turn data into a string. + * + * @param Registry class + */ +public class RegistryParser extends Parser { + + private final Registry registry; + private final String languageNode; + + private final Map names = new HashMap<>(); + private final Map parseMap = new HashMap<>(); + + public RegistryParser(Registry registry, String languageNode) { + assert !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; + this.registry = registry; + this.languageNode = languageNode; + refresh(); + Language.addListener(this::refresh); + } + + private void refresh() { + names.clear(); + parseMap.clear(); + for (R registryObject : registry) { + NamespacedKey namespacedKey = registryObject.getKey(); + String namespace = namespacedKey.getNamespace(); + String key = namespacedKey.getKey(); + String keyWithSpaces = key.replace("_", " "); + String languageKey = languageNode + "." + key; + + // Put the full namespaced key as a pattern + parseMap.put(namespacedKey.toString(), registryObject); + + // If the object is a vanilla Minecraft object, we'll add the key with spaces as a pattern + if (namespace.equalsIgnoreCase(NamespacedKey.MINECRAFT)) { + parseMap.put(keyWithSpaces, registryObject); + } + + String[] options = Language.getList(languageKey); + // Missing/Custom registry objects + if (options.length == 1 && options[0].equals(languageKey.toLowerCase(Locale.ENGLISH))) { + if (namespace.equalsIgnoreCase(NamespacedKey.MINECRAFT)) { + // If the object is a vanilla Minecraft object, we'll use the key with spaces as a name + names.put(registryObject, keyWithSpaces); + } else { + // If the object is a custom object, we'll use the full namespaced key as a name + names.put(registryObject, namespacedKey.toString()); + } + } else { + for (String option : options) { + option = option.toLowerCase(Locale.ENGLISH); + + // Isolate the gender if one is present + NonNullPair strippedOption = Noun.stripGender(option, languageKey); + String first = strippedOption.getFirst(); + Integer second = strippedOption.getSecond(); + + // Add to name map if needed + names.putIfAbsent(registryObject, first); + + parseMap.put(first, registryObject); + if (second != -1) { // There is a gender present + parseMap.put(Noun.getArticleWithSpace(second, Language.F_INDEFINITE_ARTICLE) + first, registryObject); + } + } + } + } + } + + /** + * This method attempts to match the string input against one of the string representations of the registry. + * + * @param input a string to attempt to match against one in the registry. + * @param context of parsing, may not be null + * @return The registry object matching the input, or null if no match could be made. + */ + @Override + public @Nullable R parse(String input, @NotNull ParseContext context) { + return parseMap.get(input.toLowerCase(Locale.ENGLISH)); + } + + /** + * This method returns the string representation of a registry. + * + * @param object The object to represent as a string. + * @param flags not currently used + * @return A string representation of the registry object. + */ + @Override + public @NotNull String toString(R object, int flags) { + return names.get(object); + } + + /** + * Returns a registry object's string representation in a variable name. + * + * @param object Object to represent in a variable name. + * @return The given object's representation in a variable name. + */ + @Override + public @NotNull String toVariableNameString(R object) { + return toString(object, 0); + } + + /** + * @return A comma-separated string containing a list of all names representing the registry. + * Note that some entries may represent the same registry object. + */ + public String getAllNames() { + List strings = new ArrayList<>(parseMap.keySet()); + Collections.sort(strings); + return StringUtils.join(strings, ", "); + } + +} diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java new file mode 100644 index 00000000000..ddec6c3225f --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/registry/RegistrySerializer.java @@ -0,0 +1,83 @@ +/** + * 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.registry; + +import ch.njol.skript.classes.Serializer; +import ch.njol.yggdrasil.Fields; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; + +import java.io.StreamCorruptedException; + +/** + * Serializer for {@link RegistryClassInfo} + * + * @param Registry class + */ +public class RegistrySerializer extends Serializer { + + private final Registry registry; + + public RegistrySerializer(Registry registry) { + this.registry = registry; + } + + @Override + public Fields serialize(R o) { + Fields fields = new Fields(); + fields.putPrimitive("name", o.getKey().toString()); + return null; + } + + @Override + protected R deserialize(Fields fields) { + try { + String name = fields.getAndRemovePrimitive("name", String.class); + NamespacedKey namespacedKey; + if (!name.contains(":")) { + // Old variables + namespacedKey = NamespacedKey.minecraft(name); + } else { + namespacedKey = NamespacedKey.fromString(name); + } + if (namespacedKey == null) + return null; + return registry.get(namespacedKey); + } catch (StreamCorruptedException e) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + + @Override + public void deserialize(R o, Fields f) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index d0b4d220514..21ce3d4d475 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -26,21 +26,22 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ReturnHandler; +import ch.njol.skript.lang.ReturnHandler.ReturnHandlerStack; 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; -import ch.njol.skript.lang.function.FunctionEvent; -import ch.njol.skript.lang.function.Functions; -import ch.njol.skript.lang.function.ScriptFunction; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.registrations.Classes; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @Name("Return") -@Description("Makes a function return a value") +@Description("Makes a trigger (e.g. a function) return a value") @Examples({ "function double(i: number) :: number:", "\treturn 2 * {_i}", @@ -50,90 +51,89 @@ }) @Since("2.2, 2.8.0 (returns aliases)") public class EffReturn extends Effect { - + static { Skript.registerEffect(EffReturn.class, "return %objects%"); + ParserInstance.registerData(ReturnHandlerStack.class, ReturnHandlerStack::new); } - + @SuppressWarnings("NotNullFieldNotInitialized") - private ScriptFunction function; - + private ReturnHandler handler; @SuppressWarnings("NotNullFieldNotInitialized") private Expression value; - - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - ScriptFunction f = Functions.currentFunction; - if (f == null) { - Skript.error("The return statement can only be used in a function"); + handler = getParser().getData(ReturnHandlerStack.class).getCurrentHandler(); + if (handler == null) { + Skript.error("The return statement cannot be used here"); return false; } - + if (!isDelayed.isFalse()) { Skript.error("A return statement after a delay is useless, as the calling trigger will resume when the delay starts (and won't get any returned value)"); return false; } - - function = f; - ClassInfo returnType = function.getReturnType(); + + Class returnType = handler.returnValueType(); if (returnType == null) { - Skript.error("This function doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function."); + Skript.error(handler + " doesn't return any value. Please use 'stop' or 'exit' if you want to stop the trigger."); return false; } - + RetainingLogHandler log = SkriptLogger.startRetainingLog(); Expression convertedExpr; try { - convertedExpr = exprs[0].getConvertedExpression(returnType.getC()); + convertedExpr = exprs[0].getConvertedExpression(returnType); if (convertedExpr == null) { - log.printErrors("This function is declared to return " + returnType.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type."); + String typeName = Classes.getSuperClassInfo(returnType).getName().withIndefiniteArticle(); + log.printErrors(handler + " is declared to return " + typeName + ", but " + exprs[0].toString(null, false) + " is not of that type."); return false; } log.printLog(); } finally { log.stop(); } - - if (f.isSingle() && !convertedExpr.isSingle()) { - Skript.error("This function is defined to only return a single " + returnType.toString() + ", but this return statement can return multiple values."); + + if (handler.isSingleReturnValue() && !convertedExpr.isSingle()) { + Skript.error(handler + " is defined to only return a single " + returnType + ", but this return statement can return multiple values."); return false; } value = convertedExpr; - + return true; } - + @Override @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) protected TriggerItem walk(Event event) { debug(event, false); - if (event instanceof FunctionEvent) { - ((ScriptFunction) function).setReturnValue(value.getArray(event)); - } else { - assert false : event; - } + //noinspection rawtypes,unchecked + ((ReturnHandler) handler).returnValues(value.getArray(event)); TriggerSection parent = getParent(); - while (parent != null) { + while (parent != null && parent != handler) { if (parent instanceof SectionExitHandler) ((SectionExitHandler) parent).exit(event); parent = parent.getParent(); } + if (handler instanceof SectionExitHandler) + ((SectionExitHandler) handler).exit(event); + return null; } - + @Override protected void execute(Event event) { assert false; } - + @Override public String toString(@Nullable Event event, boolean debug) { return "return " + value.toString(event, debug); } - + } diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 6cad5ec1a2c..504c4449dc7 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -23,6 +23,8 @@ import java.util.ArrayList; import java.util.List; +import ch.njol.util.Kleenean; +import org.bukkit.World; import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.Allay; import org.bukkit.entity.Animals; @@ -155,15 +157,13 @@ public final static class SimpleEntityDataInfo { final String codeName; final Class c; final boolean isSupertype; + final Kleenean allowSpawning; - SimpleEntityDataInfo(final String codeName, final Class c) { - this(codeName, c, false); - } - - SimpleEntityDataInfo(final String codeName, final Class c, final boolean isSupertype) { + SimpleEntityDataInfo(String codeName, Class c, boolean isSupertype, Kleenean allowSpawning) { this.codeName = codeName; this.c = c; this.isSupertype = isSupertype; + this.allowSpawning = allowSpawning; } @Override @@ -191,11 +191,18 @@ public boolean equals(final @Nullable Object obj) { private final static List types = new ArrayList<>(); private static void addSimpleEntity(String codeName, Class entityClass) { - types.add(new SimpleEntityDataInfo(codeName, entityClass)); + addSimpleEntity(codeName, entityClass, Kleenean.UNKNOWN); + } + + /** + * @param allowSpawning Whether to override the default {@link #canSpawn(World)} behavior and allow this entity to be spawned. + */ + private static void addSimpleEntity(String codeName, Class entityClass, Kleenean allowSpawning) { + types.add(new SimpleEntityDataInfo(codeName, entityClass, false, allowSpawning)); } private static void addSuperEntity(String codeName, Class entityClass) { - types.add(new SimpleEntityDataInfo(codeName, entityClass, true)); + types.add(new SimpleEntityDataInfo(codeName, entityClass, true, Kleenean.UNKNOWN)); } static { // Simple Entities @@ -238,7 +245,9 @@ private static void addSuperEntity(String codeName, Class enti addSimpleEntity("witch", Witch.class); addSimpleEntity("wither", Wither.class); addSimpleEntity("wither skull", WitherSkull.class); - addSimpleEntity("firework", Firework.class); + // bukkit marks fireworks as not spawnable + // see https://hub.spigotmc.org/jira/browse/SPIGOT-7677 + addSimpleEntity("firework", Firework.class, Kleenean.TRUE); addSimpleEntity("endermite", Endermite.class); addSimpleEntity("armor stand", ArmorStand.class); addSimpleEntity("shulker", Shulker.class); @@ -449,7 +458,16 @@ protected boolean equals_i(final EntityData obj) { final SimpleEntityData other = (SimpleEntityData) obj; return info.equals(other.info); } - + + @Override + public boolean canSpawn(@Nullable World world) { + if (info.allowSpawning.isUnknown()) // unspecified, refer to default behavior + return super.canSpawn(world); + if (world == null) + return false; + return info.allowSpawning.isTrue(); + } + @Override public Fields serialize() throws NotSerializableException { final Fields f = super.serialize(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprStringCase.java b/src/main/java/ch/njol/skript/expressions/ExprStringCase.java index af963a333af..7a21357dca8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprStringCase.java +++ b/src/main/java/ch/njol/skript/expressions/ExprStringCase.java @@ -153,22 +153,28 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { + String mode = ""; switch (type) { case 0: // Basic Case Change - return (casemode == 1) ? "uppercase" : "lowercase"; + mode = (casemode == 1) ? "uppercase" : "lowercase"; + break; case 1: // Proper Case - return ((casemode == 3) ? "strict" : "lenient") + " proper case"; + mode = ((casemode == 3) ? "strict" : "lenient") + " proper case"; + break; case 2: // Camel Case - return ((casemode == 3) ? "strict" : "lenient") + " camel case"; + mode = ((casemode == 3) ? "strict" : "lenient") + " camel case"; + break; case 3: // Pascal Case - return ((casemode == 3) ? "strict" : "lenient") + " pascal case"; + mode = ((casemode == 3) ? "strict" : "lenient") + " pascal case"; + break; case 4: // Snake Case - return ((casemode == 0) ? "" : ((casemode == 1)) ? "upper " : "lower ") + "snake case"; + mode = ((casemode == 0) ? "" : ((casemode == 1)) ? "upper " : "lower ") + "snake case"; + break; case 5: // Kebab Case - return ((casemode == 0) ? "" : ((casemode == 1)) ? "upper " : "lower ") + "kebab case"; + mode = ((casemode == 0) ? "" : ((casemode == 1)) ? "upper " : "lower ") + "kebab case"; } - return ""; // Shouldn't reach here anyways + return mode + " " + expr.toString(event, debug); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/lang/ReturnHandler.java b/src/main/java/ch/njol/skript/lang/ReturnHandler.java new file mode 100644 index 00000000000..f908fc68c51 --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/ReturnHandler.java @@ -0,0 +1,194 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.lang.parser.ParserInstance; +import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.Nullable; + +import java.util.Deque; +import java.util.LinkedList; + +public interface ReturnHandler { + + /** + * Loads the code in the given {@link SectionNode} using the same logic as + * {@link Section#loadCode(SectionNode)} and pushes the section onto the + * return handler stack + *
+ * This method may only be called by a {@link Section} + * @throws SkriptAPIException if this return handler is not a {@link Section} + */ + @NonExtendable + default void loadReturnableSectionCode(SectionNode node) { + if (!(this instanceof Section)) + throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object"); + ParserInstance parser = ParserInstance.get(); + ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class); + stack.push(this); + Section section = (Section) this; + try { + section.loadCode(node); + } finally { + stack.pop(); + } + } + + /** + * Loads the code in the given {@link SectionNode} using the same logic as + * {@link Section#loadCode(SectionNode, String, Class[])} and pushes the section onto the + * return handler stack + *
+ * This method may only be called by a {@link Section} + * @param node the section node + * @param name the name of the event(s) being used + * @param events the event(s) during the section's execution + * @return a returnable trigger containing the loaded section. + * This should be stored and used to run the section one or more times + * @throws SkriptAPIException if this return handler is not a {@link Section} + */ + @NonExtendable + default ReturnableTrigger loadReturnableSectionCode(SectionNode node, String name, Class[] events) { + if (!(this instanceof Section)) + throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object"); + ParserInstance parser = ParserInstance.get(); + ParserInstance.Backup parserBackup = parser.backup(); + parser.reset(); + + parser.setCurrentEvent(name, events); + SkriptEvent skriptEvent = new SectionSkriptEvent(name, (Section) this); + parser.setCurrentStructure(skriptEvent); + ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class); + + try { + return new ReturnableTrigger<>( + this, + parser.getCurrentScript(), + name, + skriptEvent, + trigger -> { + stack.push(trigger); + return ScriptLoader.loadItems(node); + } + ); + } finally { + stack.pop(); + parser.restoreBackup(parserBackup); + } + } + + /** + * Loads the code in the given {@link SectionNode} into a {@link ReturnableTrigger}. + *
+ * This is a general method to load a section node without extra logic + * done to the {@link ParserInstance}. + * The calling code is expected to manage the {@code ParserInstance} accordingly, which may vary depending on + * where the code being loaded is located and what state the {@code ParserInstance} is in. + * @param node the section node to load + * @param name the name of the trigger + * @param event the {@link SkriptEvent} of the trigger + * @return a returnable trigger containing the loaded section node + */ + @NonExtendable + default ReturnableTrigger loadReturnableTrigger(SectionNode node, String name, SkriptEvent event) { + ParserInstance parser = ParserInstance.get(); + ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class); + try { + return new ReturnableTrigger( + this, + parser.getCurrentScript(), + name, + event, + trigger -> { + stack.push(trigger); + return ScriptLoader.loadItems(node); + } + ); + } finally { + stack.pop(); + } + } + + /** + * @param values the values to return + */ + void returnValues(T @Nullable [] values); + + /** + * @return whether this return handler may accept multiple return values + */ + boolean isSingleReturnValue(); + + /** + * The return type of this return handler, or null if it can't + * accept return values in this context (e.g. a function without a return type). + * + * @return the return type + */ + @Nullable Class returnValueType(); + + class ReturnHandlerStack extends ParserInstance.Data { + + private final Deque> stack = new LinkedList<>(); + + public ReturnHandlerStack(ParserInstance parserInstance) { + super(parserInstance); + } + + public Deque> getStack() { + return stack; + } + + /** + * Retrieves the current {@link ReturnHandler} + * @return the return data + */ + public @Nullable ReturnHandler getCurrentHandler() { + return stack.peek(); + } + + /** + * Pushes the current return handler onto the return stack. + *
+ * Note: After the trigger finished loading, + * {@link ReturnHandlerStack#pop()} MUST be called + * @param handler the return handler + * @see ReturnHandlerStack#pop() + */ + public void push(ReturnHandler handler) { + stack.push(handler); + } + + /** + * Pops the current handler off the return stack. + * Should be called after the trigger has finished loading. + * @return the popped return data + * @see ReturnHandlerStack#push(ReturnHandler) + */ + public ReturnHandler pop() { + return stack.pop(); + } + + } + +} diff --git a/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java b/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java new file mode 100644 index 00000000000..51c025ef70c --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/ReturnableTrigger.java @@ -0,0 +1,53 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang; + +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +public class ReturnableTrigger extends Trigger implements ReturnHandler { + + private final ReturnHandler handler; + + public ReturnableTrigger(ReturnHandler handler, @Nullable Script script, String name, SkriptEvent event, Function, List> loadItems) { + super(script, name, event, Collections.emptyList()); + this.handler = handler; + setTriggerItems(loadItems.apply(this)); + } + + @Override + public void returnValues(T @Nullable [] values) { + handler.returnValues(values); + } + + @Override + public boolean isSingleReturnValue() { + return handler.isSingleReturnValue(); + } + + @Override + public @Nullable Class returnValueType() { + return handler.returnValueType(); + } + +} diff --git a/src/main/java/ch/njol/skript/lang/function/Functions.java b/src/main/java/ch/njol/skript/lang/function/Functions.java index bc19f2bbf72..2eee7ea6c18 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -121,7 +121,7 @@ public static Function loadFunction(Script script, SectionNode node, Signatur Skript.debug((signature.local ? "local " : "") + "function " + name + "(" + StringUtils.join(params, ", ") + ")" + (c != null ? " :: " + (signature.isSingle() ? c.getName().getSingular() : c.getName().getPlural()) : "") + ":"); - Function f = new ScriptFunction<>(signature, script, node); + Function f = new ScriptFunction<>(signature, node); // Register the function for signature namespace.addFunction(f); diff --git a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java index 259c8bcad85..d3a1e85117c 100644 --- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java @@ -18,58 +18,49 @@ */ package ch.njol.skript.lang.function; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.lang.ReturnHandler; +import org.jetbrains.annotations.ApiStatus; import org.skriptlang.skript.lang.script.Script; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.ScriptLoader; import ch.njol.skript.config.SectionNode; import ch.njol.skript.effects.EffReturn; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.util.SimpleEvent; import ch.njol.skript.variables.Variables; -/** - * @author Peter Güttinger - */ -public class ScriptFunction extends Function { - +public class ScriptFunction extends Function implements ReturnHandler { + private final Trigger trigger; - + + private boolean returnValueSet; + private T @Nullable [] returnValues; + + /** + * @deprecated use {@link ScriptFunction#ScriptFunction(Signature, SectionNode)} + */ + @Deprecated public ScriptFunction(Signature sign, Script script, SectionNode node) { + this(sign, node); + } + + public ScriptFunction(Signature sign, SectionNode node) { super(sign); - + Functions.currentFunction = this; try { - trigger = new Trigger( - script, - "function " + sign.getName(), - new SimpleEvent(), - ScriptLoader.loadItems(node) - ); - trigger.setLineNumber(node.getLine()); + trigger = loadReturnableTrigger(node, "function " + sign.getName(), new SimpleEvent()); } finally { Functions.currentFunction = null; } + trigger.setLineNumber(node.getLine()); } - - private boolean returnValueSet = false; - @Nullable - private T[] returnValue = null; - - /** - * Should only be called by {@link EffReturn}. - */ - public final void setReturnValue(final @Nullable T[] value) { - assert !returnValueSet; - returnValueSet = true; - returnValue = value; - } - + // REMIND track possible types of local variables (including undefined variables) (consider functions, commands, and EffChange) - maybe make a general interface for this purpose // REM: use patterns, e.g. {_a%b%} is like "a.*", and thus subsequent {_axyz} may be set and of that type. @Override - @Nullable - public T[] execute(final FunctionEvent e, final Object[][] params) { + public T @Nullable [] execute(final FunctionEvent e, final Object[][] params) { Parameter[] parameters = getSignature().getParameters(); for (int i = 0; i < parameters.length; i++) { Parameter p = parameters[i]; @@ -84,14 +75,42 @@ public T[] execute(final FunctionEvent e, final Object[][] params) { } trigger.execute(e); - return returnValue; + ClassInfo returnType = getReturnType(); + return returnType != null ? returnValues : null; + } + + /** + * Should only be called by {@link EffReturn}. + * @deprecated Use {@link ScriptFunction#returnValues(Object[])} + */ + @Deprecated + @ApiStatus.Internal + public final void setReturnValue(@Nullable T[] values) { + returnValues(values); } @Override public boolean resetReturnValue() { - returnValue = null; returnValueSet = false; + returnValues = null; return true; } + @Override + public final void returnValues(T @Nullable [] values) { + assert !returnValueSet; + returnValueSet = true; + this.returnValues = values; + } + + @Override + public final boolean isSingleReturnValue() { + return isSingle(); + } + + @Override + public final @Nullable Class returnValueType() { + return getReturnType() != null ? getReturnType().getC() : null; + } + } diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 12a0314b224..4348e23bb1f 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -81,6 +81,9 @@ @Since("1.0") public class StructCommand extends Structure { + // Paper versions with the new command system need a delay before syncing commands or a CME will occur. + private static final boolean DELAY_COMMAND_SYNCING = Skript.classExists("io.papermc.paper.command.brigadier.Commands"); + public static final Priority PRIORITY = new Priority(500); private static final Pattern COMMAND_PATTERN = Pattern.compile("(?i)^command\\s+/?(\\S+)\\s*(\\s+(.+))?$"); @@ -318,7 +321,7 @@ public boolean load() { @Override public boolean postLoad() { - attemptCommandSync(); + scheduleCommandSync(); return true; } @@ -331,20 +334,30 @@ public void unload() { @Override public void postUnload() { - attemptCommandSync(); + scheduleCommandSync(); } - private void attemptCommandSync() { + private void scheduleCommandSync() { if (SYNC_COMMANDS.get()) { SYNC_COMMANDS.set(false); - if (CommandReloader.syncCommands(Bukkit.getServer())) { - Skript.debug("Commands synced to clients"); + if (DELAY_COMMAND_SYNCING) { + // if the plugin is disabled, the server is likely closing and delaying will cause an error. + if (Bukkit.getPluginManager().isPluginEnabled(Skript.getInstance())) + Bukkit.getScheduler().runTask(Skript.getInstance(), this::forceCommandSync); } else { - Skript.debug("Commands changed but not synced to clients (normal on 1.12 and older)"); + forceCommandSync(); } } } + private void forceCommandSync() { + if (CommandReloader.syncCommands(Bukkit.getServer())) { + Skript.debug("Commands synced to clients"); + } else { + Skript.debug("Commands changed but not synced to clients (normal on 1.12 and older)"); + } + } + @Override public Priority getPriority() { return PRIORITY; diff --git a/src/main/java/ch/njol/skript/test/runner/SecReturnable.java b/src/main/java/ch/njol/skript/test/runner/SecReturnable.java new file mode 100644 index 00000000000..436b6d282fb --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/SecReturnable.java @@ -0,0 +1,114 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.*; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@NoDoc +public class SecReturnable extends Section implements ReturnHandler { + + static { + Skript.registerSection(SecReturnable.class, "returnable [:plural] %*classinfo% section"); + } + + private ClassInfo returnValueType; + private boolean singleReturnValue; + private static Object @Nullable [] returnedValues; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List triggerItems) { + returnValueType = ((Literal>) expressions[0]).getSingle(); + singleReturnValue = !parseResult.hasTag("plural"); + loadReturnableSectionCode(sectionNode); + return true; + } + + @Override + protected @Nullable TriggerItem walk(Event event) { + return walk(event, true); + } + + @Override + public void returnValues(Object @Nullable [] values) { + returnedValues = values; + } + + @Override + public boolean isSingleReturnValue() { + return singleReturnValue; + } + + @Override + public @Nullable Class returnValueType() { + return returnValueType.getC(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "returnable " + (singleReturnValue ? "" : "plural ") + returnValueType.toString(event, debug) + " section"; + } + + @NoDoc + public static class ExprLastReturnValues extends SimpleExpression { + + static { + Skript.registerExpression(ExprLastReturnValues.class, Object.class, ExpressionType.SIMPLE, "[the] last return[ed] value[s]"); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public @Nullable Object[] get(Event event) { + Object[] returnedValues = SecReturnable.returnedValues; + SecReturnable.returnedValues = null; + return returnedValues; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return Object.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "last returned values"; + } + + } + +} diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index bc72f768ae6..88f4bfbfe7c 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -18,9 +18,6 @@ */ package ch.njol.skript.util; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -346,20 +343,7 @@ else if (HAS_SUSPICIOUS_META && meta instanceof SuspiciousStewMeta) itemType.setItemMeta(meta); } - @Nullable - private static final MethodHandle BASE_POTION_DATA_HANDLE; - - static { - MethodHandle basePotionDataHandle = null; - if (Skript.methodExists(PotionMeta.class, "getBasePotionData")) { - try { - basePotionDataHandle = MethodHandles.lookup().findVirtual(PotionMeta.class, "getBasePotionData", MethodType.methodType(PotionData.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - Skript.exception(e, "Failed to load legacy potion data support. Potions may not work as expected."); - } - } - BASE_POTION_DATA_HANDLE = basePotionDataHandle; - } + private static final boolean HAS_POTION_TYPE_METHOD = Skript.methodExists(PotionMeta.class, "hasBasePotionType"); /** * Get all the PotionEffects of an ItemType @@ -374,21 +358,22 @@ public static List getEffects(ItemType itemType) { ItemMeta meta = itemType.getItemMeta(); if (meta instanceof PotionMeta) { PotionMeta potionMeta = ((PotionMeta) meta); - effects.addAll(potionMeta.getCustomEffects()); - if (BASE_POTION_DATA_HANDLE != null) { - try { - effects.addAll(PotionDataUtils.getPotionEffects((PotionData) BASE_POTION_DATA_HANDLE.invoke(meta))); - } catch (Throwable e) { - throw Skript.exception(e, "An error occurred while trying to invoke legacy potion data support."); + if (potionMeta.hasCustomEffects()) + effects.addAll(potionMeta.getCustomEffects()); + if (HAS_POTION_TYPE_METHOD) { + if (potionMeta.hasBasePotionType()) { + //noinspection ConstantConditions - checked via hasBasePotionType + effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); + } + } else { // use deprecated method + PotionData data = potionMeta.getBasePotionData(); + if (data != null) { + effects.addAll(PotionDataUtils.getPotionEffects(data)); } - } else if (potionMeta.hasBasePotionType()) { - //noinspection ConstantConditions - checked via hasBasePotionType - effects.addAll(potionMeta.getBasePotionType().getPotionEffects()); } - } else if (HAS_SUSPICIOUS_META && meta instanceof SuspiciousStewMeta) effects.addAll(((SuspiciousStewMeta) meta).getCustomEffects()); return effects; } - + } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 9f7df2c9e4c..90a37eb9e32 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1473,13 +1473,13 @@ visual effects: block_marker: # added in 1.18 name: block marker @a - pattern: (barrierbm:barrier|lightbm:light|%-blockdata/itemtype% block marker) + pattern: (barrierbm:barrier [particle]|lightbm:light [particle]|%-blockdata/itemtype% block marker) barrier: name: barrier @a - pattern: barrier + pattern: barrier [particle] light: # added in 1.17 name: light @- - pattern: light + pattern: light [particle] bubble: # added in 1.20.5 name: bubble @- @@ -1534,7 +1534,7 @@ visual effects: dolphin: # added in 1.13 name: dolphin @- - pattern: dolphin + pattern: dolphin [particle] dragon_breath: # added in 1.14 name: dragon breath @- @@ -1602,10 +1602,10 @@ visual effects: elder_guardian: # added in 1.20.5 name: elder guardian @- - pattern: (elder guardian|mob appearance|guardian ghost) + pattern: (elder guardian particle|mob appearance|guardian ghost) mob_appearance: # for versions below 1.20.5 name: mob appearance @- - pattern: (elder guardian|mob appearance|guardian ghost) + pattern: (elder guardian particle|mob appearance|guardian ghost) electric_spark: name: electric spark @- @@ -1627,7 +1627,7 @@ visual effects: end_rod: name: end rod @- - pattern: end rod + pattern: end rod [particle] entity_effect: # added in 1.20.5 name: entity effect @an @@ -1693,10 +1693,10 @@ visual effects: firework: # added in 1.20.5 name: firework @- - pattern: (firework|firework['s] spark) + pattern: (firework particle|firework['s] spark) fireworks_spark: # for versions below 1.20.5 name: firework's spark @- - pattern: (firework|firework['s] spark) + pattern: (firework particle|firework['s] spark) fishing: # added in 1.20.5 name: water wake @- @@ -1764,24 +1764,24 @@ visual effects: item_cobweb: # added in 1.20.5 (for 1.21) name: cobweb @- - pattern: cobweb + pattern: cobweb [item|particle] item_slime: # added in 1.20.5 name: slime @- - pattern: slime + pattern: slime [item|particle] slime: # for versions below 1.20.5 name: slime @- - pattern: slime + pattern: slime [item|particle] item_snowball: # added in 1.20.5 name: snowball @- - pattern: (snowball [break]|snow shovel|snow(man| golem) spawn) + pattern: (snowball [item|break|particle]|snow shovel|snow(man| golem) spawn) snowball: # for versions below 1.20.5 name: snowball break @- pattern: snowball break snow_shovel: # for versions below 1.20.5 name: snow shovel @- - pattern: (snowball|snow shovel|snow(man| golem) spawn) + pattern: (snowball [item|particle]|snow shovel|snow(man| golem) spawn) landing_honey: # added in 1.15 name: landing honey @- @@ -1808,7 +1808,7 @@ visual effects: mycelium: # previously town_aura, changed in 1.20.5 name: mycelium @- - pattern: (mycelium|small smoke|town aura) + pattern: (mycelium [particle]|small smoke|town aura) town_aura: name: small smoke @- pattern: (mycelium|small smoke|town aura) @@ -1931,10 +1931,10 @@ visual effects: totem_of_undying: # added in 1.20.5 name: totem of undying @a - pattern: totem [of undying] + pattern: totem [of undying] [particle] totem: # for versions below 1.20.5 name: totem @a - pattern: totem [of undying] + pattern: totem [of undying] [particle] trial omen: # added in 1.20.5 (for 1.21) name: trial omen @a @@ -1989,10 +1989,10 @@ visual effects: witch: # added in 1.20.5 name: witch @a - pattern: (witch [magic|spell]|purple spark) + pattern: (witch (magic|spell|particle)|purple spark) spell_witch: # for versions below 1.20.5 name: witch spell @a - pattern: (witch [magic|spell]|purple spark) + pattern: (witch (magic|spell|particle)|purple spark) # -- Inventory Actions -- inventory actions: diff --git a/src/test/skript/tests/misc/returns.sk b/src/test/skript/tests/misc/returns.sk new file mode 100644 index 00000000000..56e2937f7c5 --- /dev/null +++ b/src/test/skript/tests/misc/returns.sk @@ -0,0 +1,23 @@ +test "returns": + returnable string section: + return "hello" + assert last return value is "hello" with "failed single return value" + + returnable plural number section: + return (3 and 5) + assert last return values is 3 and 5 with "failed multiple return values" + + returnable integer section: + return 2.5 + assert last return value is 2 with "failed converted return value" + +test "returns (parsing)": + parse: + returnable string section: + return 1 + assert last parse logs is set with "skript shouldn't be able to return a different type" + + parse: + returnable string section: + return ("foo" and "bar") + assert last parse logs is set with "skript shouldn't be able to return multiple values for a single return trigger" diff --git a/src/test/skript/tests/regressions/6756-potion missing base type causes npe.sk b/src/test/skript/tests/regressions/6756-potion missing base type causes npe.sk new file mode 100644 index 00000000000..8ffa025b5e6 --- /dev/null +++ b/src/test/skript/tests/regressions/6756-potion missing base type causes npe.sk @@ -0,0 +1,3 @@ +test "potion missing base type": + + assert potion effects of (plain potion of mundane) is not set with "it should not have any effects" diff --git a/src/test/skript/tests/regressions/6762-cant spawn a firework.sk b/src/test/skript/tests/regressions/6762-cant spawn a firework.sk new file mode 100644 index 00000000000..f272e0bc8e0 --- /dev/null +++ b/src/test/skript/tests/regressions/6762-cant spawn a firework.sk @@ -0,0 +1,4 @@ +test "can't spawn a firework": + + spawn a firework at spawn of world "world" + assert last spawned firework is set with "firework did not spawn" diff --git a/src/test/skript/tests/regressions/pull-6760-particle itemtype conflicts.sk b/src/test/skript/tests/regressions/pull-6760-particle itemtype conflicts.sk new file mode 100644 index 00000000000..d4cac535c18 --- /dev/null +++ b/src/test/skript/tests/regressions/pull-6760-particle itemtype conflicts.sk @@ -0,0 +1,5 @@ +test "particle itemtype conflicts": + + # itemtype parsing should take priority over visual effects + assert totem of undying is an itemtype with "totem of undying is not an itemtype" + assert mycelium is an itemtype with "mycelium is not an itemtype" diff --git a/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk index b8631e79cf2..a4791d1752a 100644 --- a/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk +++ b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk @@ -16,7 +16,7 @@ test "visual effects": play dripping water at {_} play white dust with size 2 at {_} play effect at {_} - play elder guardian at {_} + play elder guardian particle at {_} play enchant at {_} play enchanted hit at {_} play end rod at {_} @@ -26,7 +26,7 @@ test "visual effects": play large explosion at {_} play explosion emitter at {_} play falling dust of air at {_} - play firework at {_} + play firework spark at {_} play fishing at {_} play flame at {_} play happy villager at {_} @@ -51,7 +51,7 @@ test "visual effects": play totem of undying at {_} play suspended at {_} play void fog at {_} - play witch at {_} + play witch particle at {_} parse if running minecraft "1.14.4": play campfire cosy smoke at {_} play campfire signal smoke at {_}