diff --git a/.github/workflows/junit-21-builds.disabled b/.github/workflows/junit-21-builds.yml similarity index 82% rename from .github/workflows/junit-21-builds.disabled rename to .github/workflows/junit-21-builds.yml index 07bc5bebbeb..1eeee5846dc 100644 --- a/.github/workflows/junit-21-builds.disabled +++ b/.github/workflows/junit-21-builds.yml @@ -1,5 +1,3 @@ -# Disabled as EasyMock 5.2.0 is required for Java 21 support -# However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) name: JUnit (MC 1.20.6+) on: diff --git a/build.gradle b/build.gradle index 415ed5b5709..069378c56f3 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ dependencies { implementation fileTree(dir: 'lib', include: '*.jar') testShadow group: 'junit', name: 'junit', version: '4.13.2' - testShadow group: 'org.easymock', name: 'easymock', version: '5.0.1' + testShadow group: 'org.easymock', name: 'easymock', version: '5.4.0' } task checkAliases { @@ -70,7 +70,7 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } -// Excludes the tests for the build task. Should be using junit, junitJava17, skriptTest, quickTest. +// Excludes the tests for the build task. Should be using JUnitQuick, JUnitJava21, JUnitJava17, skriptTest, quickTest. // We do not want tests to run for building. That's time consuming and annoying. Especially in development. test { exclude '**/*' @@ -238,8 +238,8 @@ def latestEnv = 'java21/paper-1.21.0.json' def latestJava = java21 def oldestJava = java17 -def latestJUnitEnv = 'java17/paper-1.20.4.json' -def latestJUnitJava = java17 +def latestJUnitEnv = latestEnv +def latestJUnitJava = latestJava java { toolchain.languageVersion.set(JavaLanguageVersion.of(latestJava)) @@ -269,13 +269,11 @@ tasks.register('skriptTest') { } createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestJUnitEnv, latestJUnitJava, 0, Modifiers.JUNIT) -// Disabled as EasyMock 5.2.0 is required for Java 21 support -// However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) -//createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) +createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', java17, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' - dependsOn JUnitJava17//, JUnitJava21 + dependsOn JUnitJava17, JUnitJava21 } // Build flavor configurations diff --git a/gradle.properties b/gradle.properties index befe897fdfc..a620b360bd5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.9.1 +version=2.9.2 jarName=Skript.jar testEnv=java21/paper-1.21.0 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 3ce91ce0a41..c51112be9ac 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1,21 +1,3 @@ -/** - * 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; import ch.njol.skript.aliases.Aliases; @@ -60,6 +42,7 @@ import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.registrations.Feature; import ch.njol.skript.test.runner.EffObjectives; import ch.njol.skript.test.runner.SkriptJUnitTest; import ch.njol.skript.test.runner.SkriptTestEvent; @@ -91,7 +74,6 @@ import com.google.gson.Gson; import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -109,18 +91,18 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import org.skriptlang.skript.bukkit.SkriptMetrics; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.entry.EntryValidator; import org.skriptlang.skript.lang.experiment.ExperimentRegistry; -import ch.njol.skript.registrations.Feature; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -793,57 +775,8 @@ protected void afterErrors() { }, 100); } - // Enable metrics and register custom charts - Metrics metrics = new Metrics(Skript.this, 722); // 722 is our bStats plugin ID - metrics.addCustomChart(new SimplePie("pluginLanguage", Language::getName)); - metrics.addCustomChart(new SimplePie("effectCommands", () -> - SkriptConfig.enableEffectCommands.value().toString() - )); - metrics.addCustomChart(new SimplePie("uuidsWithPlayers", () -> - SkriptConfig.usePlayerUUIDsInVariableNames.value().toString() - )); - metrics.addCustomChart(new SimplePie("playerVariableFix", () -> - SkriptConfig.enablePlayerVariableFix.value().toString() - )); - metrics.addCustomChart(new SimplePie("logVerbosity", () -> - SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') - )); - metrics.addCustomChart(new SimplePie("pluginPriority", () -> - SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') - )); - metrics.addCustomChart(new SimplePie("logPlayerCommands", () -> - String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value())) - )); - metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> - SkriptConfig.maxTargetBlockDistance.value().toString() - )); - metrics.addCustomChart(new SimplePie("softApiExceptions", () -> - SkriptConfig.apiSoftExceptions.value().toString() - )); - metrics.addCustomChart(new SimplePie("timingsStatus", () -> { - if (!Skript.classExists("co.aikar.timings.Timings")) - return "unsupported"; - return SkriptConfig.enableTimings.value().toString(); - })); - metrics.addCustomChart(new SimplePie("parseLinks", () -> - ChatMessages.linkParseMode.name().toLowerCase(Locale.ENGLISH) - )); - metrics.addCustomChart(new SimplePie("colorResetCodes", () -> - SkriptConfig.colorResetCodes.value().toString() - )); - metrics.addCustomChart(new SimplePie("functionsWithNulls", () -> - SkriptConfig.executeFunctionsWithMissingParams.value().toString() - )); - metrics.addCustomChart(new SimplePie("buildFlavor", () -> { - if (updater != null) - return updater.getCurrentRelease().flavor; - return "unknown"; - })); - metrics.addCustomChart(new SimplePie("updateCheckerEnabled", () -> - SkriptConfig.checkForNewVersion.value().toString() - )); - metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value)); - Skript.metrics = metrics; + Skript.metrics = new Metrics(Skript.getInstance(), 722); // 722 is our bStats plugin ID + SkriptMetrics.setupMetrics(Skript.metrics); /* * Start loading scripts diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index ee715ce038e..436331d1fc6 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -1,21 +1,3 @@ -/** - * 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; import ch.njol.skript.config.Config; @@ -43,7 +25,7 @@ import ch.njol.skript.variables.Variables; import co.aikar.timings.Timings; import org.bukkit.event.EventPriority; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -79,13 +61,13 @@ public class SkriptConfig { } }); - static final Option checkForNewVersion = new Option<>("check for new version", false) + public static final Option checkForNewVersion = new Option<>("check for new version", false) .setter(t -> { SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) updater.setEnabled(t); }); - static final Option updateCheckInterval = new Option<>("update check interval", new Timespan(12 * 60 * 60 * 1000)) + public static final Option updateCheckInterval = new Option<>("update check interval", new Timespan(12 * 60 * 60 * 1000)) .setter(t -> { SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) @@ -93,7 +75,7 @@ public class SkriptConfig { }); static final Option updaterDownloadTries = new Option<>("updater download tries", 7) .optional(true); - static final Option releaseChannel = new Option<>("release channel", "none") + public static final Option releaseChannel = new Option<>("release channel", "none") .setter(t -> { ReleaseChannel channel; switch (t) { @@ -140,7 +122,7 @@ public class SkriptConfig { @SuppressWarnings("null") private static final DateFormat shortDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - private static final Option dateFormat = new Option<>("date format", shortDateFormat, s -> { + public static final Option dateFormat = new Option<>("date format", shortDateFormat, s -> { try { if (s.equalsIgnoreCase("default")) return null; @@ -158,7 +140,7 @@ public static String formatDate(final long timestamp) { } } - static final Option verbosity = new Option<>("verbosity", Verbosity.NORMAL, new EnumParser<>(Verbosity.class, "verbosity")) + public static final Option verbosity = new Option<>("verbosity", Verbosity.NORMAL, new EnumParser<>(Verbosity.class, "verbosity")) .setter(SkriptLogger::setVerbosity); public static final Option defaultEventPriority = new Option<>("plugin priority", EventPriority.NORMAL, s -> { diff --git a/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java b/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java deleted file mode 100644 index fd596a324a4..00000000000 --- a/src/main/java/ch/njol/skript/bukkitutil/AdventureSoundReceiver.java +++ /dev/null @@ -1,108 +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.bukkitutil; - -import java.util.Locale; -import java.util.OptionalLong; - -import org.bukkit.Location; -import org.bukkit.NamespacedKey; -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.entity.Entity; -import org.jetbrains.annotations.NotNull; - -import ch.njol.skript.effects.EffPlaySound; - -/** - * A utility interface to access the Player::playSound while also providing the same arguments to World::playSound - * Used in EffPlaySound. Separated due to static versioning. - */ -@FunctionalInterface -public interface AdventureSoundReceiver { - - void play( - @NotNull T receiver, @NotNull E emitter, @NotNull String sound, - @NotNull SoundCategory category, float volume, float pitch - ); - - static void play( - @NotNull AdventureEmitterSoundReceiver adventureLocationReceiver, - @NotNull AdventureEntitySoundReceiver adventureEmitterReceiver, - @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, - @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed - ) { - for (String sound : sounds) { - NamespacedKey key = null; - try { - Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - key = enumSound.getKey(); - } catch (IllegalArgumentException alternative) { - sound = sound.toLowerCase(Locale.ENGLISH); - if (!EffPlaySound.KEY_PATTERN.matcher(sound).matches()) - continue; - try { - key = NamespacedKey.fromString(sound); - } catch (IllegalArgumentException argument) { - // The user input invalid characters - } - } - - if (key == null) - continue; - net.kyori.adventure.sound.Sound adventureSound = net.kyori.adventure.sound.Sound.sound() - .source(category) - .volume(volume) - .pitch(pitch) - .seed(seed) - .type(key) - .build(); - AdventureEmitterSoundReceiver.play(adventureLocationReceiver, adventureEmitterReceiver, receiver, adventureSound, emitter); - } - } - - @FunctionalInterface - public interface AdventureEmitterSoundReceiver { - void play( - @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, double x, double y, double z - ); - - static void play( - @NotNull AdventureEmitterSoundReceiver locationReceiver, - @NotNull AdventureEntitySoundReceiver emitterReceiver, - @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, @NotNull E emitter - ) { - if (emitter instanceof Location) { - Location location = (Location) emitter; - locationReceiver.play(receiver, sound, location.getX(), location.getY(), location.getZ()); - } else if (emitter instanceof Entity) { - Entity entity = (Entity) emitter; - emitterReceiver.play(receiver, sound, entity); - } - } - } - - @FunctionalInterface - public interface AdventureEntitySoundReceiver { - void play( - @NotNull T receiver, @NotNull net.kyori.adventure.sound.Sound sound, net.kyori.adventure.sound.Sound.Emitter emitter - ); - } - -} diff --git a/src/main/java/ch/njol/skript/bukkitutil/sounds/AdventureSoundUtils.java b/src/main/java/ch/njol/skript/bukkitutil/sounds/AdventureSoundUtils.java new file mode 100644 index 00000000000..eeba746983b --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/sounds/AdventureSoundUtils.java @@ -0,0 +1,57 @@ +package ch.njol.skript.bukkitutil.sounds; + +import net.kyori.adventure.sound.Sound; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.SoundCategory; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.util.OptionalLong; + +public class AdventureSoundUtils { + + public static Sound getAdventureSound(NamespacedKey key, SoundCategory category, float volume, float pitch, OptionalLong seed) { + return Sound.sound() + .source(category) + .volume(volume) + .pitch(pitch) + .seed(seed) + .type(key) + .build(); + } + + public static void playSound(World world, Location location, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + world.playSound( + AdventureSoundUtils.getAdventureSound(sound, category, volume, pitch, seed), + location.x(), + location.y(), + location.z() + ); + } + + public static void playSound(World world, Entity entity, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + world.playSound( + AdventureSoundUtils.getAdventureSound(sound, category, volume, pitch, seed), + entity + ); + } + + public static void playSound(Player player, Location location, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + player.playSound( + AdventureSoundUtils.getAdventureSound(sound, category, volume, pitch, seed), + location.x(), + location.y(), + location.z() + ); + } + + public static void playSound(Player player, Entity entity, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + player.playSound( + AdventureSoundUtils.getAdventureSound(sound, category, volume, pitch, seed), + entity + ); + } + +} diff --git a/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java b/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java new file mode 100644 index 00000000000..b9533a30142 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java @@ -0,0 +1,134 @@ +package ch.njol.skript.bukkitutil.sounds; + +import ch.njol.skript.Skript; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.util.Locale; +import java.util.OptionalLong; + +/** + * Adapter pattern to unify {@link World} and {@link Player} playSound methods. + * Methods can be called without determining version support, it is handled internally. + * Non-supported methods will simply delegate to supported methods. + */ +public interface SoundReceiver { + + boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder"); + boolean SPIGOT_SOUND_SEED = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class, long.class); + boolean ENTITY_EMITTER_SOUND = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); + boolean ENTITY_EMITTER_STRING = Skript.methodExists(Player.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); + + void playSound(Location location, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed); + void playSound(Entity entity, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed); + + static SoundReceiver of(Player player) { return new PlayerSoundReceiver(player); } + static SoundReceiver of(World world) { return new WorldSoundReceiver(world); } + + // Player adapter pattern + class PlayerSoundReceiver implements SoundReceiver { + + private final Player player; + + protected PlayerSoundReceiver(Player player) { + this.player = player; + } + + @Override + public void playSound(Location location, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + //noinspection DuplicatedCode + if (ADVENTURE_API) { + AdventureSoundUtils.playSound(player, location, sound, category, volume, pitch, seed); + } else if (!SPIGOT_SOUND_SEED || seed.isEmpty()) { + player.playSound(location, sound.getKey(), category, volume, pitch); + } else { + player.playSound(location, sound.getKey(), category, volume, pitch, seed.getAsLong()); + } + } + + private void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) { + //noinspection DuplicatedCode + if (ENTITY_EMITTER_STRING) { + player.playSound(entity, sound, category, volume, pitch); + } else if (ENTITY_EMITTER_SOUND) { + Sound enumSound; + try { + enumSound = Sound.valueOf(sound.replace('.','_').toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + return; + } + player.playSound(entity, enumSound, category, volume, pitch); + } else { + player.playSound(entity.getLocation(), sound, category, volume, pitch); + } + } + + @Override + public void playSound(Entity entity, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + //noinspection DuplicatedCode + if (ADVENTURE_API) { + AdventureSoundUtils.playSound(player, entity, sound, category, volume, pitch, seed); + } else if (!SPIGOT_SOUND_SEED || seed.isEmpty()) { + this.playSound(entity, sound.getKey(), category, volume, pitch); + } else { + player.playSound(entity, sound.getKey(), category, volume, pitch, seed.getAsLong()); + } + } + } + + // World adapter pattern + class WorldSoundReceiver implements SoundReceiver { + + private final World world; + + protected WorldSoundReceiver(World world) { + this.world = world; + } + + @Override + public void playSound(Location location, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + //noinspection DuplicatedCode + if (ADVENTURE_API) { + AdventureSoundUtils.playSound(world, location, sound, category, volume, pitch, seed); + } else if (!SPIGOT_SOUND_SEED || seed.isEmpty()) { + world.playSound(location, sound.getKey(), category, volume, pitch); + } else { + world.playSound(location, sound.getKey(), category, volume, pitch, seed.getAsLong()); + } + } + + private void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) { + //noinspection DuplicatedCode + if (ENTITY_EMITTER_STRING) { + world.playSound(entity, sound, category, volume, pitch); + } else if (ENTITY_EMITTER_SOUND) { + Sound enumSound; + try { + enumSound = Sound.valueOf(sound.replace('.','_').toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + return; + } + world.playSound(entity, enumSound, category, volume, pitch); + } else { + world.playSound(entity.getLocation(), sound, category, volume, pitch); + } + } + + @Override + public void playSound(Entity entity, NamespacedKey sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + //noinspection DuplicatedCode + if (ADVENTURE_API) { + AdventureSoundUtils.playSound(world, entity, sound, category, volume, pitch, seed); + } else if (!SPIGOT_SOUND_SEED || seed.isEmpty()) { + this.playSound(entity, sound.getKey(), category, volume, pitch); + } else { + world.playSound(entity, sound.getKey(), category, volume, pitch, seed.getAsLong()); + } + } + } +} diff --git a/src/main/java/ch/njol/skript/bukkitutil/sounds/package-info.java b/src/main/java/ch/njol/skript/bukkitutil/sounds/package-info.java new file mode 100644 index 00000000000..48481510810 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/sounds/package-info.java @@ -0,0 +1,24 @@ +/** + * 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 + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package ch.njol.skript.bukkitutil.sounds; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/main/java/ch/njol/skript/command/CommandUsage.java b/src/main/java/ch/njol/skript/command/CommandUsage.java index 59b79765604..c746d93cf8a 100644 --- a/src/main/java/ch/njol/skript/command/CommandUsage.java +++ b/src/main/java/ch/njol/skript/command/CommandUsage.java @@ -46,6 +46,10 @@ public class CommandUsage { */ public CommandUsage(@Nullable VariableString usage, String defaultUsage) { if (usage == null) { + // Manually escape quotes. This is not a good solution, as it doesn't handle many other issues, like % in + // commands, but in lieu of re-writing the argument parser and command logic completely, I believe this is + // a decent stop-gap measure for using " in commands. + defaultUsage = VariableString.quote(defaultUsage); usage = VariableString.newInstance(defaultUsage); assert usage != null; } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsOfType.java b/src/main/java/ch/njol/skript/conditions/CondIsOfType.java index 5ca721081bc..1a02f2a4ab4 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsOfType.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsOfType.java @@ -20,6 +20,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemType; @@ -38,11 +39,8 @@ import ch.njol.util.Checker; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Is of Type") -@Description("Checks whether an item of an entity is of the given type. This is mostly useful for variables," + +@Description("Checks whether an item or an entity is of the given type. This is mostly useful for variables," + " as you can use the general 'is' condition otherwise (e.g. 'victim is a creeper').") @Examples({"tool is of type {selected type}", "victim is of type {villager type}"}) @@ -50,17 +48,17 @@ public class CondIsOfType extends Condition { static { - PropertyCondition.register(CondIsOfType.class, "of type[s] %entitytypes/entitydatas%", "itemstacks/entities"); + PropertyCondition.register(CondIsOfType.class, "of type[s] %itemtypes/entitydatas%", "itemstacks/entities"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression what; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression types; @SuppressWarnings("null") @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { what = exprs[0]; types = exprs[1]; setNegated(matchedPattern == 1); @@ -68,12 +66,12 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final } @Override - public boolean check(final Event e) { - return what.check(e, - (Checker) o1 -> types.check(e, + public boolean check(Event event) { + return what.check(event, + (Checker) o1 -> types.check(event, (Checker) o2 -> { - if (o2 instanceof ItemType && o1 instanceof ItemType) { - return ((ItemType) o2).isSupertypeOf((ItemType) o1); + if (o2 instanceof ItemType && o1 instanceof ItemStack) { + return ((ItemType) o2).isSupertypeOf(new ItemType((ItemStack) o1)); } else if (o2 instanceof EntityData && o1 instanceof Entity) { return ((EntityData) o2).isInstance((Entity) o1); } else if (o2 instanceof ItemType && o1 instanceof Entity) { @@ -86,9 +84,9 @@ public boolean check(final Event e) { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return PropertyCondition.toString(this, PropertyType.BE, e, debug, what, - "of " + (types.isSingle() ? "type " : "types") + types.toString(e, debug)); + public String toString(@Nullable Event event, boolean debug) { + return PropertyCondition.toString(this, PropertyType.BE, event, debug, what, + "of " + (types.isSingle() ? "type " : "types ") + types.toString(event, debug)); } } diff --git a/src/main/java/ch/njol/skript/config/Option.java b/src/main/java/ch/njol/skript/config/Option.java index 13a9f113cf6..e1f3e441573 100644 --- a/src/main/java/ch/njol/skript/config/Option.java +++ b/src/main/java/ch/njol/skript/config/Option.java @@ -1,34 +1,15 @@ -/** - * 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.config; -import java.util.Locale; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.Parser; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; import ch.njol.util.Setter; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converter; + +import java.util.Locale; /** * @author Peter Güttinger @@ -119,6 +100,10 @@ protected void onValueChange() { public final T value() { return parsedValue; } + + public final T defaultValue() { + return defaultValue; + } public final boolean isOptional() { return optional; diff --git a/src/main/java/ch/njol/skript/config/SectionNode.java b/src/main/java/ch/njol/skript/config/SectionNode.java index 07661898321..f43ffbbe9eb 100644 --- a/src/main/java/ch/njol/skript/config/SectionNode.java +++ b/src/main/java/ch/njol/skript/config/SectionNode.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.config; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Pattern; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.validate.EntryValidator; @@ -39,6 +27,16 @@ import ch.njol.util.NullableChecker; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; /** * @author Peter Güttinger diff --git a/src/main/java/ch/njol/skript/effects/EffLook.java b/src/main/java/ch/njol/skript/effects/EffLook.java index 39f1d95de5f..6a45f5ed84a 100644 --- a/src/main/java/ch/njol/skript/effects/EffLook.java +++ b/src/main/java/ch/njol/skript/effects/EffLook.java @@ -40,7 +40,7 @@ @Name("Look At") @Description("Forces the mob(s) or player(s) to look at an entity, vector or location. Vanilla max head pitches range from 10 to 50.") @Examples({ - "force the head of the player to look towards event-entity's feet", + "force the player to look towards event-entity's feet", "", "on entity explosion:", "\tset {_player} to the nearest player", diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index 686f7334671..2846d616f06 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -1,27 +1,7 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.AdventureSoundReceiver; -import ch.njol.skript.bukkitutil.AdventureSoundReceiver.AdventureEmitterSoundReceiver; -import ch.njol.skript.bukkitutil.AdventureSoundReceiver.AdventureEntitySoundReceiver; +import ch.njol.skript.bukkitutil.sounds.SoundReceiver; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -35,13 +15,13 @@ import org.bukkit.NamespacedKey; import org.bukkit.Sound; import org.bukkit.SoundCategory; -import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.OptionalLong; import java.util.regex.Matcher; @@ -54,7 +34,7 @@ "Spigot sound names " + "are supported. Playing resource pack sounds are supported too. The sound category is 'master' by default. ", "", - "When running 1.18+, playing a sound from an entity directly will result in the sound coming from said entity, even while moving.", + "When running 1.19+, playing a sound from an entity directly will result in the sound coming from said entity, even while moving.", "If the sound is custom, a location emitter will follow the entity. Do note that pitch and volume ", "are reflected based on the entity, and Minecraft may not use the values from this syntax.", "", @@ -64,31 +44,46 @@ "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself.", }) @Examples({ - "play sound \"block.note_block.pling\" # It is block.note.pling in 1.12.2", + "play sound \"block.note_block.pling\"", "play sound \"entity.experience_orb.pickup\" with volume 0.5 to the player", "play sound \"custom.music.1\" in jukebox category at {speakerBlock}", "play sound \"BLOCK_AMETHYST_BLOCK_RESONATE\" with seed 1 on target entity for the player #1.20.1+" }) @RequiredPlugins("Minecraft 1.18.1+ (entity emitters), Paper 1.19.4+ or Adventure API 4.12.0+ (sound seed)") -@Since("2.2-dev28, 2.4 (sound categories), 2.9.0 (sound seed & entity emitter)") +@Since("2.2-dev28, 2.4 (sound categories), 2.9 (sound seed & entity emitter)") public class EffPlaySound extends Effect { + // <=1.17: + // Player - Location - Sound/String + // World - Location - Sound/String + // 1.18: + // Player - Location - Sound/String + // World - Location - Sound/String + // Player - Entity - Sound + // World - Entity - Sound + // 1.19: + // Player - Location/Entity - Sound/String + // World - Location/Entity - Sound/String + // 1.20 - spigot adds sound seeds + private static final boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder"); - private static final boolean PLAYER_ENTITY_EMITTER = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); - private static final boolean WORLD_ENTITY_EMITTER = Skript.methodExists(World.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); + private static final boolean SPIGOT_SOUND_SEED = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class, long.class); + private static final boolean HAS_SEED = ADVENTURE_API || SPIGOT_SOUND_SEED; + private static final boolean ENTITY_EMITTER_SOUND = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); + private static final boolean ENTITY_EMITTER_STRING = Skript.methodExists(Player.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); + private static final boolean ENTITY_EMITTER = ENTITY_EMITTER_SOUND || ENTITY_EMITTER_STRING; + public static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?([a-z0-9/._-]+)"); static { - String additional = ""; - if (ADVENTURE_API) - additional = "[[with] seed %-number%] "; + String seedOption = HAS_SEED ? "[[with] seed %-number%] " : ""; String emitterTypes = "locations"; - if (PLAYER_ENTITY_EMITTER) + if (ENTITY_EMITTER) emitterTypes += "/entities"; Skript.registerEffect(EffPlaySound.class, - "play sound[s] %strings% " + additional + "[(in|from) %-soundcategory%] " + + "play sound[s] %strings% " + seedOption + "[(in|from) %-soundcategory%] " + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] (at|on|from) %" + emitterTypes + "% [(to|for) %-players%]", - "play sound[s] %strings% " + additional + "[(in|from) %-soundcategory%] " + + "play sound[s] %strings% " + seedOption + "[(in|from) %-soundcategory%] " + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|on|from) %-" + emitterTypes + "%]" ); } @@ -119,7 +114,7 @@ public class EffPlaySound extends Effect { public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { sounds = (Expression) exprs[0]; int index = 1; - if (ADVENTURE_API) + if (HAS_SEED) seed = (Expression) exprs[index++]; category = (Expression) exprs[index++]; volume = (Expression) exprs[index++]; @@ -151,37 +146,73 @@ protected void execute(Event event) { .orElse(1) .floatValue(); + // validate strings + List validSounds = new ArrayList<>(); + for (String sound : sounds.getArray(event)) { + NamespacedKey key = null; + try { + Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); + key = enumSound.getKey(); + } catch (IllegalArgumentException alternative) { + sound = sound.toLowerCase(Locale.ENGLISH); + Matcher keyMatcher = KEY_PATTERN.matcher(sound); + if (!keyMatcher.matches()) + continue; + try { + String namespace = keyMatcher.group(1); + String keyValue = keyMatcher.group(2); + if (namespace == null) { + key = NamespacedKey.minecraft(keyValue); + } else { + namespace = namespace.substring(0, namespace.length() - 1); + key = new NamespacedKey(namespace, keyValue); + } + } catch (IllegalArgumentException argument) { + // The user input invalid characters + } + } + + if (key == null) + continue; + validSounds.add(key); + } + + if (validSounds.isEmpty()) + return; + + // play sounds if (players != null) { if (emitters == null) { for (Player player : players.getArray(event)) { - play(PLAYER_ENTITY_EMITTER ? Player::playSound : null, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, - player, player.getLocation(), sounds.getArray(event), category, volume, pitch, seed); + SoundReceiver receiver = SoundReceiver.of(player); + Location emitter = player.getLocation(); + for (NamespacedKey sound : validSounds) + receiver.playSound(emitter, sound, category, volume, pitch, seed); } } else { for (Player player : players.getArray(event)) { + SoundReceiver receiver = SoundReceiver.of(player); for (Object emitter : emitters.getArray(event)) { - if (emitter instanceof Entity && PLAYER_ENTITY_EMITTER) { - Entity entity = (Entity) emitter; - play(Player::playSound, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, - player, entity, sounds.getArray(event), category, volume, pitch, seed); - } else if (emitter instanceof Location) { - Location location = (Location) emitter; - play(PLAYER_ENTITY_EMITTER ? Player::playSound : null, Player::playSound, ADVENTURE_API ? Player::playSound : null, ADVENTURE_API ? Player::playSound : null, - player, location, sounds.getArray(event), category, volume, pitch, seed); + if (emitter instanceof Location) { + for (NamespacedKey sound : validSounds) + receiver.playSound(((Location) emitter), sound, category, volume, pitch, seed); + } else if (emitter instanceof Entity) { + for (NamespacedKey sound : validSounds) + receiver.playSound(((Entity) emitter), sound, category, volume, pitch, seed); } } } } } else if (emitters != null) { for (Object emitter : emitters.getArray(event)) { - if (emitter instanceof Entity && WORLD_ENTITY_EMITTER) { - Entity entity = (Entity) emitter; - play(World::playSound, World::playSound, ADVENTURE_API ? World::playSound : null, ADVENTURE_API ? World::playSound : null, - entity.getWorld(), entity, sounds.getArray(event), category, volume, pitch, seed); + if (ENTITY_EMITTER && emitter instanceof Entity) { + SoundReceiver receiver = SoundReceiver.of(((Entity) emitter).getWorld()); + for (NamespacedKey sound : validSounds) + receiver.playSound(((Entity) emitter), sound, category, volume, pitch, seed); } else if (emitter instanceof Location) { - Location location = (Location) emitter; - play(WORLD_ENTITY_EMITTER ? World::playSound : null, World::playSound, ADVENTURE_API ? World::playSound : null, ADVENTURE_API ? World::playSound : null, - location.getWorld(), location, sounds.getArray(event), category, volume, pitch, seed); + SoundReceiver receiver = SoundReceiver.of(((Location) emitter).getWorld()); + for (NamespacedKey sound : validSounds) + receiver.playSound(((Location) emitter), sound, category, volume, pitch, seed); } } } @@ -209,66 +240,4 @@ public String toString(@Nullable Event event, boolean debug) { return builder.toString(); } - private void play(@Nullable SoundReceiver entityReceiver, - @NotNull SoundReceiver locationReceiver, - @Nullable AdventureEmitterSoundReceiver adventureLocationReceiver, - @Nullable AdventureEntitySoundReceiver adventureEmitterReceiver, - @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, - @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed) { - if (!ADVENTURE_API || adventureLocationReceiver == null || adventureEmitterReceiver == null) { - SoundReceiver.play(entityReceiver, locationReceiver, receiver, emitter, sounds, category, volume, pitch, seed); - return; - } - AdventureSoundReceiver.play(adventureLocationReceiver, adventureEmitterReceiver, receiver, emitter, sounds, category, volume, pitch, seed); - } - - @FunctionalInterface - private interface SoundReceiver { - void play( - @NotNull T receiver, @NotNull E emitter, @NotNull String sound, - @NotNull SoundCategory category, float volume, float pitch - ); - - static void play( - @Nullable SoundReceiver entityReceiver, - @NotNull SoundReceiver locationReceiver, - @NotNull T receiver, @NotNull E emitter, @NotNull String[] sounds, - @NotNull SoundCategory category, float volume, float pitch, OptionalLong seed - ) { - for (String sound : sounds) { - NamespacedKey key = null; - try { - Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - key = enumSound.getKey(); - } catch (IllegalArgumentException alternative) { - sound = sound.toLowerCase(Locale.ENGLISH); - Matcher keyMatcher = KEY_PATTERN.matcher(sound); - if (!keyMatcher.matches()) - continue; - try { - String namespace = keyMatcher.group(1); - String keyValue = keyMatcher.group(2); - if (namespace == null) { - key = NamespacedKey.minecraft(keyValue); - } else { - namespace = namespace.substring(0, namespace.length() - 1); - key = new NamespacedKey(namespace, keyValue); - } - } catch (IllegalArgumentException argument) { - // The user input invalid characters - } - } - - if (key == null) - continue; - if (emitter instanceof Location) { - locationReceiver.play(receiver, (Location) emitter, key.getKey(), category, volume, pitch); - } else if (emitter instanceof Entity && entityReceiver != null) { - entityReceiver.play(receiver, (Entity) emitter, key.getKey(), category, volume, pitch); - } - return; - } - } - } - } diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 7c4025b5d7a..ba56ae3e875 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -96,7 +96,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } if (handler.isSingleReturnValue() && !convertedExpr.isSingle()) { - Skript.error(handler + " is defined to only return a single " + returnType + ", but this return statement can return multiple values."); + String typeName = Classes.getSuperClassInfo(returnType).getName().getSingular(); + Skript.error(handler + " is defined to only return a single " + typeName + ", but this return statement can return multiple values."); return false; } value = convertedExpr; diff --git a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java index 64cfca1c603..a3b58b6b9d3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java @@ -1,60 +1,40 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.expressions; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; -import com.destroystokyo.paper.profile.PlayerProfile; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.*; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; @Name("Hover List") -@Description({"The list when you hover on the player counts of the server in the server list.", - "This can be changed using texts or players in a server list ping event only. " + - "Adding players to the list means adding the name of the players.", - "And note that, for example if there are 5 online players (includes fake online count) " + - "in the server and the hover list is set to 3 values, Minecraft will show \"... and 2 more ...\" at end of the list."}) -@Examples({"on server list ping:", +@Description({ + "The list when you hover on the player counts of the server in the server list.", + "This can be changed using texts or players in a server list ping event only. " + + "Adding players to the list means adding the name of the players.", + "And note that, for example if there are 5 online players (includes fake online count) " + + "in the server and the hover list is set to 3 values, Minecraft will show \"... and 2 more ...\" at end of the list." +}) +@Examples({ + "on server list ping:", "\tclear the hover list", "\tadd \"&aWelcome to the &6Minecraft &aserver!\" to the hover list", "\tadd \"\" to the hover list # A blank line", - "\tadd \"&cThere are &6%online players count% &conline players!\" to the hover list"}) + "\tadd \"&cThere are &6%online players count% &conline players!\" to the hover list" +}) @Since("2.3") @RequiredPlugins("Paper 1.12.2 or newer") @Events("server list ping") @@ -62,11 +42,12 @@ public class ExprHoverList extends SimpleExpression { static { Skript.registerExpression(ExprHoverList.class, String.class, ExpressionType.SIMPLE, - "[the] [custom] [(player|server)] (hover|sample) ([message] list|message)", - "[the] [custom] player [(hover|sample)] list"); + "[the] [custom] [player|server] (hover|sample) ([message] list|message)", + "[the] [custom] player [hover|sample] list"); } private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private static final boolean HAS_NEW_LISTED_PLAYER_INFO = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo"); @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { @@ -82,13 +63,19 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - public String[] get(Event e) { - if (!(e instanceof PaperServerListPingEvent)) + public String[] get(Event event) { + if (!(event instanceof PaperServerListPingEvent)) return null; - return ((PaperServerListPingEvent) e).getPlayerSample().stream() + if (HAS_NEW_LISTED_PLAYER_INFO) { + return ((PaperServerListPingEvent) event).getListedPlayers().stream() + .map(PaperServerListPingEvent.ListedPlayerInfo::name) + .toArray(String[]::new); + } else { + return ((PaperServerListPingEvent) event).getPlayerSample().stream() .map(PlayerProfile::getName) .toArray(String[]::new); + } } @Override @@ -111,24 +98,56 @@ public Class[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (!(e instanceof PaperServerListPingEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof PaperServerListPingEvent)) return; + if (HAS_NEW_LISTED_PLAYER_INFO) { + List values = new ArrayList<>(); + if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) { + for (Object object : delta) { + if (object instanceof Player) { + Player player = (Player) object; + values.add(new PaperServerListPingEvent.ListedPlayerInfo(player.getName(), player.getUniqueId())); + } else { + values.add(new PaperServerListPingEvent.ListedPlayerInfo((String) object, UUID.randomUUID())); + } + } + } + + List sample = ((PaperServerListPingEvent) event).getListedPlayers(); + switch (mode) { + case SET: + sample.clear(); + // $FALL-THROUGH$ + case ADD: + sample.addAll(values); + break; + case REMOVE: + sample.removeAll(values); + break; + case DELETE: + case RESET: + sample.clear(); + break; + } + return; + } + List values = new ArrayList<>(); if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) { - for (Object o : delta) { - if (o instanceof Player) { - Player player = ((Player) o); + for (Object object : delta) { + if (object instanceof Player) { + Player player = (Player) object; values.add(Bukkit.createProfile(player.getUniqueId(), player.getName())); } else { - values.add(Bukkit.createProfile(UUID.randomUUID(), (String) o)); + values.add(Bukkit.createProfile(UUID.randomUUID(), (String) object)); } } } - List sample = ((PaperServerListPingEvent) e).getPlayerSample(); - switch (mode){ + List sample = ((PaperServerListPingEvent) event).getPlayerSample(); + switch (mode) { case SET: sample.clear(); sample.addAll(values); @@ -142,6 +161,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { case DELETE: case RESET: sample.clear(); + break; } } @@ -160,4 +180,4 @@ public String toString(@Nullable Event e, boolean debug) { return "the hover list"; } -} \ No newline at end of file +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java b/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java index 24b59ae9dd3..2c425bdd373 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java @@ -1,32 +1,15 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.expressions; +import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.EntityUtils; -import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.TrialSpawner; import org.bukkit.entity.EntityType; import org.bukkit.event.Event; +import org.bukkit.spawner.TrialSpawnerConfiguration; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -39,55 +22,93 @@ @Name("Spawner Type") @Description("Retrieves, sets, or resets the spawner's entity type") -@Examples({"on right click:", - " if event-block is spawner:", - " send \"Spawner's type is %target block's entity type%\""}) -@Since("2.4") +@Examples({ + "on right click:", + "\tif event-block is spawner:", + "\t\tsend \"Spawner's type is %target block's entity type%\"" +}) +@Since("2.4, 2.9.2 (trial spawner)") public class ExprSpawnerType extends SimplePropertyExpression { - + + private static final boolean HAS_TRIAL_SPAWNER = Skript.classExists("org.bukkit.block.TrialSpawner"); + static { register(ExprSpawnerType.class, EntityData.class, "(spawner|entity|creature) type[s]", "blocks"); } - - @Override + @Nullable public EntityData convert(Block block) { - if (!(block.getState() instanceof CreatureSpawner)) - return null; - EntityType type = ((CreatureSpawner) block.getState()).getSpawnedType(); - if (type == null) - return null; - return EntityUtils.toSkriptEntityData(type); + if (block.getState() instanceof CreatureSpawner) { + EntityType type = ((CreatureSpawner) block.getState()).getSpawnedType(); + if (type == null) + return null; + return EntityUtils.toSkriptEntityData(type); + } + if (HAS_TRIAL_SPAWNER && block.getState() instanceof TrialSpawner) { + TrialSpawner trialSpawner = (TrialSpawner) block.getState(); + EntityType type; + if (trialSpawner.isOminous()) { + type = trialSpawner.getOminousConfiguration().getSpawnedType(); + } else { + type = trialSpawner.getNormalConfiguration().getSpawnedType(); + } + if (type == null) + return null; + return EntityUtils.toSkriptEntityData(type); + } + return null; } @Nullable @Override public Class[] acceptChange(Changer.ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.RESET) - return CollectionUtils.array(EntityData.class); - return null; + switch (mode) { + case SET: + case RESET: + return CollectionUtils.array(EntityData.class); + default: + return null; + } } @SuppressWarnings("null") @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - for (Block b : getExpr().getArray(event)) { - if (!(b.getState() instanceof CreatureSpawner)) - continue; - CreatureSpawner s = (CreatureSpawner) b.getState(); - switch (mode) { - case SET: - assert delta != null; - s.setSpawnedType(EntityUtils.toBukkitEntityType((EntityData) delta[0])); - break; - case RESET: - s.setSpawnedType(org.bukkit.entity.EntityType.PIG); - break; + for (Block block : getExpr().getArray(event)) { + if (block.getState() instanceof CreatureSpawner) { + CreatureSpawner spawner = (CreatureSpawner) block.getState(); + switch (mode) { + case SET: + assert delta != null; + spawner.setSpawnedType(EntityUtils.toBukkitEntityType((EntityData) delta[0])); + break; + case RESET: + spawner.setSpawnedType(EntityType.PIG); + break; + } + spawner.update(); // Actually trigger the spawner's update + } else if (HAS_TRIAL_SPAWNER && block.getState() instanceof TrialSpawner) { + TrialSpawner trialSpawner = (TrialSpawner) block.getState(); + TrialSpawnerConfiguration config; + if (trialSpawner.isOminous()) { + config = trialSpawner.getOminousConfiguration(); + } else { + config = trialSpawner.getNormalConfiguration(); + } + switch (mode) { + case SET: + assert delta != null; + config.setSpawnedType((EntityUtils.toBukkitEntityType((EntityData) delta[0]))); + break; + case RESET: + config.setSpawnedType(EntityType.PIG); + break; + } + trialSpawner.update(); } - s.update(); // Actually trigger the spawner's update } } - + @Override public Class getReturnType() { return EntityData.class; diff --git a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java index edd0e054113..e8bd9d652b9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java @@ -68,7 +68,8 @@ public class ExprVehicle extends SimplePropertyExpression { register(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); // legacy support - boolean hasOldMountEvents = Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); + boolean hasOldMountEvents = !HAS_NEW_MOUNT_EVENTS && + Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); Class oldMountEventClass = null; MethodHandle oldGetMountHandle = null; Class oldDismountEventClass = null; diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index 4ae72d5977d..8579dc2386f 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Map; +import com.google.common.collect.Queues; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; @@ -85,7 +86,16 @@ public class StructVariables extends Structure { public static class DefaultVariables implements ScriptData { - private final Deque[]>> hints = new ArrayDeque<>(); + /* + * Performance/Risk Notice: + * In the event that an element is pushed to the deque on one thread, causing it to grow, a second thread + * waiting to access the dequeue may not see the correct deque or pointers (the backing array is not volatile), + * causing issues such as a loss of data or attempting to write beyond the array's capacity. + * It is unlikely for the array to ever grow from its default capacity (16), as this would require extreme + * nesting of variables (e.g. {a::%{b::%{c::}%}%} (given the current usage of enter/exit scope) + * While thread-safe deque implementations are available, this setup has been chosen for performance. + */ + private final Deque[]>> hints = Queues.synchronizedDeque(new ArrayDeque<>()); private final List> variables; private boolean loaded; @@ -99,9 +109,10 @@ public void add(String variable, Class... hints) { if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint. return; // This important empty check ensures that the variable type hint came from a defined DefaultVariable. - if (this.hints.isEmpty()) + Map[]> map = this.hints.peekFirst(); + if (map == null) return; - this.hints.getFirst().put(variable, hints); + map.put(variable, hints); } public void enterScope() { @@ -119,12 +130,13 @@ public void exitScope() { * @param variable The variable string of a variable. * @return type hints of a variable if found otherwise null. */ - @Nullable - public Class[] get(String variable) { - for (Map[]> map : hints) { - Class[] hints = map.get(variable); - if (hints != null && hints.length > 0) - return hints; + public Class @Nullable [] get(String variable) { + synchronized (hints) { // must manually synchronize for iterators + for (Map[]> map : hints) { + Class[] hints = map.get(variable); + if (hints != null && hints.length > 0) + return hints; + } } return null; } diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 879e1aabe74..2cda7d3109b 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -22,12 +22,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Random; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.jar.JarEntry; @@ -42,7 +37,6 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageListener; -import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.Iterables; import com.google.common.io.ByteArrayDataInput; @@ -62,19 +56,64 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EnumerationIterable; import net.md_5.bungee.api.ChatColor; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.NotNull; /** * Utility class. - * + * * @author Peter Güttinger */ public abstract class Utils { - - private Utils() {} - + public final static Random random = new Random(); - + protected final static Deque plurals = new LinkedList<>(); + + static { + plurals.add(new WordEnding("axe", "axes")); + plurals.add(new WordEnding("x", "xes")); + + plurals.add(new WordEnding("ay", "ays")); + plurals.add(new WordEnding("ey", "eys")); + plurals.add(new WordEnding("iy", "iys")); + plurals.add(new WordEnding("oy", "oys")); + plurals.add(new WordEnding("uy", "uys")); + plurals.add(new WordEnding("kie", "kies")); + plurals.add(new WordEnding("zombie", "zombies")); + plurals.add(new WordEnding("y", "ies")); + + plurals.add(new WordEnding("wife", "wives")); // we have to do the -ife -> ives first + plurals.add(new WordEnding("life", "lives")); + plurals.add(new WordEnding("knife", "knives")); + plurals.add(new WordEnding("ive", "ives")); + plurals.add(new WordEnding("elf", "elves")); // self shelf elf + plurals.add(new WordEnding("fe", "ves"));// most -f words' plurals can end in -fs as well as -ves + + plurals.add(new WordEnding("h", "hes")); + + plurals.add(new WordEnding("man", "men")); + + plurals.add(new WordEnding("ui", "uis")); // gui fix + plurals.add(new WordEnding("api", "apis")); // api fix + plurals.add(new WordEnding("us", "i")); + + plurals.add(new WordEnding("hoe", "hoes")); + plurals.add(new WordEnding("toe", "toes")); + plurals.add(new WordEnding("o", "oes")); + + plurals.add(new WordEnding("alias", "aliases")); + plurals.add(new WordEnding("gas", "gases")); + + plurals.add(new WordEnding("child", "children")); + + plurals.add(new WordEnding("sheep", "sheep")); + + // general ending + plurals.add(new WordEnding("", "s")); + } + + private Utils() {} + public static String join(final Object[] objects) { assert objects != null; final StringBuilder b = new StringBuilder(); @@ -85,7 +124,7 @@ public static String join(final Object[] objects) { } return "" + b.toString(); } - + public static String join(final Iterable objects) { assert objects != null; final StringBuilder b = new StringBuilder(); @@ -99,12 +138,12 @@ public static String join(final Iterable objects) { } return "" + b.toString(); } - + @SuppressWarnings("unchecked") public static boolean isEither(@Nullable T compared, @Nullable T... types) { return CollectionUtils.contains(types, compared); } - + public static Pair getAmount(String s) { if (s.matches("\\d+ of .+")) { return new Pair<>(s.split(" ", 3)[2], Utils.parseInt("" + s.split(" ", 2)[0])); @@ -115,7 +154,7 @@ public static Pair getAmount(String s) { } return new Pair<>(s, Integer.valueOf(-1)); } - + // public final static class AmountResponse { // public final String s; // public final int amount; @@ -163,7 +202,7 @@ public static Pair getAmount(String s) { /** * Loads classes of the plugin by package. Useful for registering many syntax elements like Skript does it. - * + * * @param basePackage The base package to add to all sub packages, e.g. "ch.njol.skript". * @param subPackages Which subpackages of the base package should be loaded, e.g. "expressions", "conditions", "effects". Subpackages of these packages will be loaded * as well. Use an empty array to load all subpackages of the base package. @@ -216,7 +255,7 @@ public static Class[] getClasses(Plugin plugin, String basePackage, String... /** * The first invocation of this method uses reflection to invoke the protected method {@link JavaPlugin#getFile()} to get the plugin's jar file. - * + * * @return The jar file of the plugin. */ @Nullable @@ -239,80 +278,42 @@ public static File getFile(Plugin plugin) { return null; } - private final static String[][] plurals = { - - {"fe", "ves"},// most -f words' plurals can end in -fs as well as -ves - - {"axe", "axes"}, - {"x", "xes"}, - - {"ay", "ays"}, - {"ey", "eys"}, - {"iy", "iys"}, - {"oy", "oys"}, - {"uy", "uys"}, - {"kie", "kies"}, - {"zombie", "zombies"}, - {"y", "ies"}, - - {"h", "hes"}, - - {"man", "men"}, - - {"us", "i"}, - - {"hoe", "hoes"}, - {"toe", "toes"}, - {"o", "oes"}, - - {"alias", "aliases"}, - {"gas", "gases"}, - - {"child", "children"}, - - {"sheep", "sheep"}, - - // general ending - {"", "s"}, - }; - /** - * @param s trimmed string + * @param word trimmed string * @return Pair of singular string + boolean whether it was plural */ - @SuppressWarnings("null") - public static NonNullPair getEnglishPlural(final String s) { - assert s != null; - if (s.isEmpty()) + public static NonNullPair getEnglishPlural(String word) { + assert word != null; + if (word.isEmpty()) return new NonNullPair<>("", Boolean.FALSE); - for (final String[] p : plurals) { - if (s.endsWith(p[1])) - return new NonNullPair<>(s.substring(0, s.length() - p[1].length()) + p[0], Boolean.TRUE); - if (s.endsWith(p[1].toUpperCase(Locale.ENGLISH))) - return new NonNullPair<>(s.substring(0, s.length() - p[1].length()) + p[0].toUpperCase(Locale.ENGLISH), Boolean.TRUE); + for (final WordEnding ending : plurals) { + if (word.endsWith(ending.plural())) + return new NonNullPair<>(word.substring(0, word.length() - ending.plural().length()) + ending.singular(), Boolean.TRUE); + if (word.endsWith(ending.plural().toUpperCase(Locale.ENGLISH))) + return new NonNullPair<>(word.substring(0, word.length() - ending.plural().length()) + ending.singular().toUpperCase(Locale.ENGLISH), Boolean.TRUE); } - return new NonNullPair<>(s, Boolean.FALSE); + return new NonNullPair<>(word, Boolean.FALSE); } - + /** * Gets the english plural of a word. - * - * @param s + * + * @param word * @return The english plural of the given word */ - public static String toEnglishPlural(final String s) { - assert s != null && s.length() != 0; - for (final String[] p : plurals) { - if (s.endsWith(p[0])) - return s.substring(0, s.length() - p[0].length()) + p[1]; + public static String toEnglishPlural(String word) { + assert word != null && word.length() != 0; + for (WordEnding ending : plurals) { + if (word.endsWith(ending.singular())) + return word.substring(0, word.length() - ending.singular().length()) + ending.plural(); } assert false; - return s + "s"; + return word + "s"; } - + /** * Gets the plural of a word (or not if p is false) - * + * * @param s * @param p * @return The english plural of the given word, or the word itself if p is false. @@ -322,10 +323,10 @@ public static String toEnglishPlural(final String s, final boolean p) { return toEnglishPlural(s); return s; } - + /** * Adds 'a' or 'an' to the given string, depending on the first character of the string. - * + * * @param s The string to add the article to * @return The given string with an appended a/an and a space at the beginning * @see #A(String) @@ -334,10 +335,10 @@ public static String toEnglishPlural(final String s, final boolean p) { public static String a(final String s) { return a(s, false); } - + /** * Adds 'A' or 'An' to the given string, depending on the first character of the string. - * + * * @param s The string to add the article to * @return The given string with an appended A/An and a space at the beginning * @see #a(String) @@ -346,10 +347,10 @@ public static String a(final String s) { public static String A(final String s) { return a(s, true); } - + /** * Adds 'a' or 'an' to the given string, depending on the first character of the string. - * + * * @param s The string to add the article to * @param capA Whether to use a capital a or not * @return The given string with an appended a/an (or A/An if capA is true) and a space at the beginning @@ -367,14 +368,14 @@ public static String a(final String s, final boolean capA) { return "a " + s; } } - + /** * Gets the collision height of solid or partially-solid blocks at the center of the block. * This is mostly for use in the {@link EffTeleport teleport effect}. *

* This version operates on numeric ids, thus only working on * Minecraft 1.12 or older. - * + * * @param type * @return The block's height at the center */ @@ -530,14 +531,14 @@ public static CompletableFuture sendPluginMessage(Player pla return completableFuture; } - + final static ChatColor[] styles = {ChatColor.BOLD, ChatColor.ITALIC, ChatColor.STRIKETHROUGH, ChatColor.UNDERLINE, ChatColor.MAGIC, ChatColor.RESET}; final static Map chat = new HashMap<>(); final static Map englishChat = new HashMap<>(); - + public final static boolean HEX_SUPPORTED = Skript.isRunningMinecraft(1, 16); public final static boolean COPY_SUPPORTED = Skript.isRunningMinecraft(1, 15); - + static { Language.addListener(new LanguageChangeListener() { @Override @@ -554,21 +555,21 @@ public void onLanguageChange() { } }); } - + @Nullable public static String getChatStyle(final String s) { SkriptColor color = SkriptColor.fromName(s); - + if (color != null) return color.getFormattedChat(); return chat.get(s); } - + private final static Pattern stylePattern = Pattern.compile("<([^<>]+)>"); - + /** * Replaces <chat styles> in the message - * + * * @param message * @return message with localised chat styles converted to Minecraft's format */ @@ -602,11 +603,11 @@ public String run(final Matcher m) { m = ChatColor.translateAlternateColorCodes('&', "" + m); return "" + m; } - + /** * Replaces english <chat styles> in the message. This is used for messages in the language file as the language of colour codes is not well defined while the language is * changing, and for some hardcoded messages. - * + * * @param message * @return message with english chat styles converted to Minecraft's format */ @@ -653,7 +654,7 @@ public String run(final Matcher m) { public static ChatColor parseHexColor(String hex) { if (!HEX_SUPPORTED || !HEX_PATTERN.matcher(hex).matches()) // Proper hex code validation return null; - + hex = hex.replace("#", ""); try { return ChatColor.of('#' + hex.substring(0, 6)); @@ -661,10 +662,10 @@ public static ChatColor parseHexColor(String hex) { return null; } } - + /** * Gets a random value between start (inclusive) and end (exclusive) - * + * * @param start * @param end * @return start + random.nextInt(end - start) @@ -733,12 +734,12 @@ public static Class highestDenominator(Class< // See #1747 to learn how it broke returning items from functions return (Class) (chosen == Cloneable.class ? bestGuess : chosen == Object.class ? bestGuess : chosen); } - + /** * Parses a number that was validated to be an integer but might still result in a {@link NumberFormatException} when parsed with {@link Integer#parseInt(String)} due to * overflow. * This method will return {@link Integer#MIN_VALUE} or {@link Integer#MAX_VALUE} respectively if that happens. - * + * * @param s * @return The parsed integer, {@link Integer#MIN_VALUE} or {@link Integer#MAX_VALUE} respectively */ @@ -750,12 +751,12 @@ public static int parseInt(final String s) { return s.startsWith("-") ? Integer.MIN_VALUE : Integer.MAX_VALUE; } } - + /** * Parses a number that was validated to be an integer but might still result in a {@link NumberFormatException} when parsed with {@link Long#parseLong(String)} due to * overflow. * This method will return {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} respectively if that happens. - * + * * @param s * @return The parsed long, {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} respectively */ @@ -767,7 +768,7 @@ public static long parseLong(final String s) { return s.startsWith("-") ? Long.MIN_VALUE : Long.MAX_VALUE; } } - + /** * Gets class for name. Throws RuntimeException instead of checked one. * Use this only when absolutely necessary. @@ -783,7 +784,7 @@ public static Class classForName(String name) { throw new RuntimeException("Class not found!"); } } - + /** * Finds the index of the last in a {@link List} that matches the given {@link Checker}. * @@ -807,5 +808,37 @@ public static boolean isInteger(Number... numbers) { } return true; } - + + protected static class WordEnding { // To be a record in 2.10 + + private final String singular, plural; + + private WordEnding(String singular, String plural) { + this.singular = singular; + this.plural = plural; + } + + public String singular() { + return singular; + } + + public String plural() { + return plural; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof WordEnding)) return false; + WordEnding ending = (WordEnding) object; + return Objects.equals(singular, ending.singular) && Objects.equals(plural, ending.plural); + } + + @Override + public int hashCode() { + return Objects.hash(singular, plural); + } + + } + } diff --git a/src/main/java/org/skriptlang/skript/bukkit/SkriptMetrics.java b/src/main/java/org/skriptlang/skript/bukkit/SkriptMetrics.java new file mode 100644 index 00000000000..e57c4ec7700 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/SkriptMetrics.java @@ -0,0 +1,268 @@ +package org.skriptlang.skript.bukkit; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.config.Option; +import ch.njol.skript.localization.Language; +import ch.njol.skript.update.Updater; +import ch.njol.skript.util.Version; +import ch.njol.skript.util.chat.ChatMessages; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.DrilldownPie; +import org.bstats.charts.SimplePie; +import org.jetbrains.annotations.Nullable; + +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * helper class to handle bstats metrics + */ +public class SkriptMetrics { + + /** + * Helper method to set up bstats charts on the supplied Metrics object + * @param metrics The Metrics object to which charts will be added. + */ + public static void setupMetrics(Metrics metrics) { + // Enable metrics and register custom charts + + // sets up the old charts to prevent data splitting due to various user version + setupLegacyMetrics(metrics); + + // add custom version charts for easier reading: + metrics.addCustomChart(new DrilldownPie("drilldownPluginVersion", () -> { + Version version = Skript.getVersion(); + Table table = HashBasedTable.create(1,1); + table.put( + version.getMajor() + "." + version.getMinor(), // upper label + version.toString(), // lower label + 1 // weight + ); + return table.rowMap(); + })); + + metrics.addCustomChart(new DrilldownPie("drilldownMinecraftVersion", () -> { + Version version = Skript.getMinecraftVersion(); + Table table = HashBasedTable.create(1,1); + table.put( + version.getMajor() + "." + version.getMinor(), // upper label + version.toString(), // lower label + 1 // weight + ); + return table.rowMap(); + })); + + metrics.addCustomChart(new SimplePie("buildFlavor", () -> { + Updater updater = Skript.getInstance().getUpdater(); + if (updater != null) + return updater.getCurrentRelease().flavor; + return "unknown"; + })); + + // + // config options + // + + metrics.addCustomChart(new DrilldownPie("drilldownPluginLanguage", () -> { + String lang = Language.getName(); + return isDefaultMap(lang, SkriptConfig.language.defaultValue()); + })); + + metrics.addCustomChart(new DrilldownPie("drilldownUpdateChecker", () -> { + Table table = HashBasedTable.create(1,1); + table.put( + SkriptConfig.checkForNewVersion.value().toString(), // upper label + SkriptConfig.updateCheckInterval.value().toString(), // lower label + 1 // weight + ); + return table.rowMap(); + })); + metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value)); + + // effect commands + metrics.addCustomChart(new DrilldownPie("drilldownEffectCommands", () -> { + Table table = HashBasedTable.create(1,1); + table.put( + SkriptConfig.enableEffectCommands.value().toString(), // upper label + SkriptConfig.effectCommandToken.value(), // lower label + 1 // weight + ); + return table.rowMap(); + })); + metrics.addCustomChart(new SimplePie("effectCommandsOps", () -> + SkriptConfig.allowOpsToUseEffectCommands.value().toString() + )); + metrics.addCustomChart(new SimplePie("logEffectCommands", () -> + SkriptConfig.logEffectCommands.value().toString() + )); + + metrics.addCustomChart(new SimplePie("loadDefaultAliases", () -> + SkriptConfig.loadDefaultAliases.value().toString() + )); + + metrics.addCustomChart(new SimplePie("playerVariableFix", () -> + SkriptConfig.enablePlayerVariableFix.value().toString() + )); + metrics.addCustomChart(new SimplePie("uuidsWithPlayers", () -> + SkriptConfig.usePlayerUUIDsInVariableNames.value().toString() + )); + + metrics.addCustomChart(new DrilldownPie("drilldownDateFormat", () -> { + String value = ((SimpleDateFormat) SkriptConfig.dateFormat.value()).toPattern(); + String defaultValue = ((SimpleDateFormat) SkriptConfig.dateFormat.defaultValue()).toPattern(); + return isDefaultMap(value, defaultValue, "default"); + })); + + metrics.addCustomChart(new DrilldownPie("drilldownLogVerbosity", () -> { + String verbosity = SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' '); + return isDefaultMap(verbosity, SkriptConfig.verbosity.defaultValue()); + })); + + metrics.addCustomChart(new DrilldownPie("drilldownPluginPriority", () -> { + String priority = SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' '); + return isDefaultMap(priority, SkriptConfig.defaultEventPriority.defaultValue()); + })); + metrics.addCustomChart(new SimplePie("cancelledByDefault", () -> + SkriptConfig.listenCancelledByDefault.value().toString() + )); + + metrics.addCustomChart(new DrilldownPie("drilldownNumberAccuracy", () -> + isDefaultMap(SkriptConfig.numberAccuracy) + )); + + metrics.addCustomChart(new DrilldownPie("drilldownMaxTargetDistance", () -> + isDefaultMap(SkriptConfig.maxTargetBlockDistance) + )); + + metrics.addCustomChart(new SimplePie("caseSensitiveFunctions", () -> + SkriptConfig.caseSensitive.value().toString() + )); + metrics.addCustomChart(new SimplePie("caseSensitiveVariables", () -> + SkriptConfig.caseInsensitiveVariables.value().toString() + )); + metrics.addCustomChart(new SimplePie("caseSensitiveCommands", () -> + SkriptConfig.caseInsensitiveCommands.value().toString() + )); + + metrics.addCustomChart(new SimplePie("disableSaveWarnings", () -> + SkriptConfig.disableObjectCannotBeSavedWarnings.value().toString() + )); + metrics.addCustomChart(new SimplePie("disableAndOrWarnings", () -> + SkriptConfig.disableMissingAndOrWarnings.value().toString() + )); + metrics.addCustomChart(new SimplePie("disableStartsWithWarnings", () -> + SkriptConfig.disableVariableStartingWithExpressionWarnings.value().toString() + )); + + metrics.addCustomChart(new SimplePie("softApiExceptions", () -> + SkriptConfig.apiSoftExceptions.value().toString() + )); + + metrics.addCustomChart(new SimplePie("timingsStatus", () -> { + if (!Skript.classExists("co.aikar.timings.Timings")) + return "unsupported"; + return SkriptConfig.enableTimings.value().toString(); + })); + + metrics.addCustomChart(new SimplePie("parseLinks", () -> + ChatMessages.linkParseMode.name().toLowerCase(Locale.ENGLISH) + )); + + metrics.addCustomChart(new SimplePie("colorResetCodes", () -> + SkriptConfig.colorResetCodes.value().toString() + )); + + metrics.addCustomChart(new SimplePie("keepLastUsage", () -> + SkriptConfig.keepLastUsageDates.value().toString() + )); + + metrics.addCustomChart(new DrilldownPie("drilldownParsetimeWarningThreshold", () -> + isDefaultMap(SkriptConfig.longParseTimeWarningThreshold, "disabled") + )); + } + + /** + * Helper method to set up legacy charts (pre 2.9.2) + * @param metrics The Metrics object to which charts will be added. + */ + private static void setupLegacyMetrics(Metrics metrics) { + // Enable metrics and register legacy charts + metrics.addCustomChart(new SimplePie("pluginLanguage", Language::getName)); + metrics.addCustomChart(new SimplePie("updateCheckerEnabled", () -> + SkriptConfig.checkForNewVersion.value().toString() + )); + metrics.addCustomChart(new SimplePie("logVerbosity", () -> + SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') + )); + metrics.addCustomChart(new SimplePie("pluginPriority", () -> + SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') + )); + metrics.addCustomChart(new SimplePie("effectCommands", () -> + SkriptConfig.enableEffectCommands.value().toString() + )); + metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> + SkriptConfig.maxTargetBlockDistance.value().toString() + )); + } + + /** + * Provides a Map for use with a {@link DrilldownPie} chart. Meant to be used in cases where a single default option has majority share, with many or custom alternative options. + * Creates a chart where the default option is presented against "other", then clicking on "other" shows the alternative options. + * @param value The option the user chose. + * @param defaultValue The default option for this chart. + * @return A Map that can be returned directly to a {@link DrilldownPie}. + * @param The type of the option. + */ + private static Map> isDefaultMap(@Nullable T value, T defaultValue) { + return isDefaultMap(value, defaultValue ,defaultValue.toString()); + } + + /** + * Provides a Map for use with a {@link DrilldownPie} chart. Meant to be used in cases where a single default option has majority share, with many or custom alternative options. + * Creates a chart where the default option is presented against "other", then clicking on "other" shows the alternative options. + * @param value The option the user chose. + * @param defaultValue The default option for this chart. + * @param defaultLabel The label to use as the default option for this chart + * @return A Map that can be returned directly to a {@link DrilldownPie}. + * @param The type of the option. + */ + private static Map> isDefaultMap(@Nullable T value, @Nullable T defaultValue, String defaultLabel) { + Table table = HashBasedTable.create(1,1); + table.put( + Objects.equals(value, defaultValue) ? defaultLabel : "other", // upper label + String.valueOf(value), // lower label + 1 // weight + ); + return table.rowMap(); + } + + /** + * Provides a Map for use with a {@link DrilldownPie} chart. Meant to be used in cases where a single default option has majority share, with many or custom alternative options. + * Creates a chart where the default option is presented against "other", then clicking on "other" shows the alternative options. + * @param option The {@link Option} from which to pull the current and default values + * @return A Map that can be returned directly to a {@link DrilldownPie}. + * @param The type of the option. + */ + private static Map> isDefaultMap(Option option) { + return isDefaultMap(option.value(), option.defaultValue(), option.defaultValue().toString()); + + } + + /** + * Provides a Map for use with a {@link DrilldownPie} chart. Meant to be used in cases where a single default option has majority share, with many or custom alternative options. + * Creates a chart where the default option is presented against "other", then clicking on "other" shows the alternative options. + * @param option The {@link Option} from which to pull the current and default values + * @param defaultLabel The label to use as the default option for this chart + * @return A Map that can be returned directly to a {@link DrilldownPie}. + * @param The type of the option. + */ + private static Map> isDefaultMap(Option option, String defaultLabel) { + return isDefaultMap(option.value(), option.defaultValue(), defaultLabel); + } + +} diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index ff5e1e9e40a..eb0b295c288 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -91,9 +91,9 @@ load default aliases: true player variable fix: true -# Whether to enable the player variable fix if a player has rejoined and was reciding inside a variable. -# Player objects inside a variable(list or normal) are not updated to the new player object -# A server creates whenever a player rejoins. +# Whether to enable the player variable fix if a player has rejoined and was residing inside a variable. +# Player objects inside a variable (list or normal) are not updated to the new player object +# that a server creates whenever a player rejoins. # Basically the variable holds the old player object when a player has rejoined thus rendering the variable kinda broken. # This fix should work around that and whenever a invalid(old) player object is attempted to be get through a variable # It will check if the player is online and then get the valid(new) player object and update the variable object to that one. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 59fa474fec6..fec454c2ac5 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2227,7 +2227,7 @@ attribute types: generic.scale: generic scale, scale generic.step_height: generic step height, step height generic.water_movement_efficiency: generic water movement efficiency, water movement efficiency - horse_jump_strength: horse jump strength + horse.jump_strength: horse jump strength player.block_break_speed: player block break speed, block break speed player.block_interaction_range: player block interaction range, block interaction range player.entity_interaction_range: player entity interaction range, entity interaction range diff --git a/src/main/resources/lang/dutch.lang b/src/main/resources/lang/dutch.lang new file mode 100644 index 00000000000..8169cc1286b --- /dev/null +++ b/src/main/resources/lang/dutch.lang @@ -0,0 +1,191 @@ +# Default Dutch language file + +# Which version of Skript this language file was written for +version: @version@ + +# What null (nothing) should show up as in a string/text +none: + +# -- Skript -- +skript: + copyright: ~ gemaakt door & © Peter Güttinger oftewel Njol ~ + prefix: [Skript] + quotes error: Ongeldig gebruik van aanhalingstekens ("). Als je aanhalingstekens wil gebruiken in "aangehaalde tekst", verdubbel ze dan: "". + brackets error: Ongeldige hoeveelheid of plaatsing van haakjes. Zorg ervoor dat elke openend haakje een bijbehorend sluitend haakje heeft. + invalid reload: Skript mag alleen worden herladen door Bukkits '/reload' of Skripts '/skript reload' commando. + no scripts: Er zijn geen scripts gevonden, misschien moet je er een paar schrijven ;) + no errors: Alle scripts zijn zonder fouten geladen. + scripts loaded: %s scripts zijn geladen met in totaal %s structuren in %s + finished loading: Klaar met laden. + +# -- Skript command -- +skript command: + usage: Gebruiksaanwijzing: + help: + description: Skripts hoofdcommando + help: Toont deze hulpmelding. Gebruik '/skript reload/enable/disable/update' voor meer informatie. + reload: + description: Herlaadt een specifiek script, alle scripts, de configuratie of alles + all: Herlaadt de configuratie, alle alliassenconfiguraties en alle scripts + config: Herlaadt de hoofdconfiguratie + aliases: Herlaadt de alliassenconfiguratie (aliases-english.zip of plugin jar) + scripts: Herlaadt alle scripts +